diff --git a/.eslintignore b/.eslintignore index 68d426174ac..c9a25670a90 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,10 +8,10 @@ storybook-static **/webpack.*.js **/jest.config.js -**/gulpfile.js apps/browser/config/config.js apps/browser/src/auth/scripts/duo.js +apps/browser/webpack/manifest.js apps/desktop/desktop_native apps/desktop/src/auth/scripts/duo.js diff --git a/.eslintrc.json b/.eslintrc.json index c606b8f933b..3fd6dec3d7e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -56,6 +56,7 @@ "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], "no-console": "error", "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. @@ -141,8 +142,7 @@ } ] } - ], - "no-restricted-imports": ["error", { "patterns": ["src/**/*"] }] + ] } }, { @@ -164,147 +164,6 @@ "tailwindcss/no-contradicting-classname": "error" } }, - { - "files": ["libs/admin-console/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/admin-console/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/angular/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/angular/*", "src/**/*"] }] - } - }, - { - "files": ["libs/auth/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/auth/*", "src/**/*"] }] - } - }, - { - "files": ["libs/billing/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/billing/*", "src/**/*"] }] - } - }, - { - "files": ["libs/common/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/common/*", "src/**/*"] }] - } - }, - { - "files": ["libs/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/components/*", "src/**/*", "@bitwarden/angular/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-components/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/history/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-history/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/legacy/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-legacy/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/navigation/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-navigation/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-ui/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/importer/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/importer/*", "src/**/*"] }] - } - }, - { - "files": ["libs/node/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }] - } - }, - { - "files": ["libs/platform/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/platform/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/send/send-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/send-ui/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/card/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/tools-card/*", "src/**/*"] }] - } - }, - { - "files": ["libs/vault/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/vault/*", "src/**/*"] }] - } - }, { "files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"], "excludedFiles": [ @@ -344,10 +203,30 @@ ] } }, + { + "files": ["**/src/**/*.ts"], + "excludedFiles": ["**/platform/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + "**/platform/**/internal", // General internal pattern + // All features that have been converted to barrel files + "**/platform/messaging/**", + "**/src/**/*" // Prevent relative imports across libs. + ] + } + ] + } + }, { "files": ["bitwarden_license/bit-common/src/**/*.ts"], "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }] + "no-restricted-imports": [ + "error", + { "patterns": ["@bitwarden/bit-common/*", "**/src/**/*"] } + ] } }, { @@ -357,7 +236,12 @@ "no-restricted-imports": [ "error", { - "patterns": ["biwarden_license/**", "@bitwarden/bit-common/*", "@bitwarden/bit-web/*"] + "patterns": [ + "biwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + "**/src/**/*" + ] } ], // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93693f183c3..cb36d87b9e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,10 +4,6 @@ # # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -## Secrets Manager team files ## -bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/team-secrets-manager-dev -apps/web/src/app/secrets-manager/ @bitwarden/team-secrets-manager-dev - ## Auth team files ## apps/browser/src/auth @bitwarden/team-auth-dev apps/cli/src/auth @bitwarden/team-auth-dev @@ -71,6 +67,7 @@ bitwarden_license/bit-web/src/app/billing @bitwarden/team-billing-dev ## Platform team files ## apps/browser/src/platform @bitwarden/team-platform-dev apps/cli/src/platform @bitwarden/team-platform-dev +apps/desktop/macos @bitwarden/team-platform-dev apps/desktop/src/platform @bitwarden/team-platform-dev apps/web/src/app/platform @bitwarden/team-platform-dev libs/angular/src/platform @bitwarden/team-platform-dev @@ -86,23 +83,42 @@ apps/web/src/utils/ @bitwarden/team-platform-dev 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 +.github/workflows/brew-bump-desktop.yml @bitwarden/team-platform-dev +.github/workflows/build-browser.yml @bitwarden/team-platform-dev +.github/workflows/build-cli.yml @bitwarden/team-platform-dev +.github/workflows/build-desktop.yml @bitwarden/team-platform-dev +.github/workflows/build-web.yml @bitwarden/team-platform-dev +.github/workflows/chromatic.yml @bitwarden/team-platform-dev +.github/workflows/lint.yml @bitwarden/team-platform-dev +.github/workflows/locales-lint.yml @bitwarden/team-platform-dev +.github/workflows/repository-management.yml @bitwarden/team-platform-dev +.github/workflows/scan.yml @bitwarden/team-platform-dev +.github/workflows/test.yml @bitwarden/team-platform-dev +.github/workflows/version-auto-bump.yml @bitwarden/team-platform-dev ## Autofill team files ## apps/browser/src/autofill @bitwarden/team-autofill-dev apps/desktop/src/autofill @bitwarden/team-autofill-dev libs/common/src/autofill @bitwarden/team-autofill-dev +apps/desktop/macos/autofill-extension @bitwarden/team-autofill-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev -apps/desktop/src/services/native-message-handler.service.ts @bitwarden/team-autofill-dev +apps/desktop/src/services/duckduckgo-message-handler.service.ts @bitwarden/team-autofill-dev +# SSH Agent +apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-dev @bitwarden/wg-ssh-keys ## Component Library ## .storybook @bitwarden/team-design-system libs/components @bitwarden/team-design-system apps/browser/src/platform/popup/layout @bitwarden/team-design-system +apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-design-system apps/web/src/app/layouts @bitwarden/team-design-system ## Desktop native module ## 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 ## Key management team files ## apps/desktop/src/key-management @bitwarden/team-key-management-dev @@ -110,10 +126,12 @@ apps/web/src/app/key-management @bitwarden/team-key-management-dev apps/browser/src/key-management @bitwarden/team-key-management-dev apps/cli/src/key-management @bitwarden/team-key-management-dev libs/key-management @bitwarden/team-key-management-dev +libs/common/src/key-management @bitwarden/team-key-management-dev apps/desktop/destkop_native/core/src/biometric/ @bitwarden/team-key-management-dev apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-key-management-dev apps/browser/src/background/nativeMessaging.background.ts @bitwarden/team-key-management-dev +apps/desktop/src/services/biometric-message-handler.service.ts @bitwarden/team-key-management-dev ## Locales ## apps/browser/src/_locales/en/messages.json @@ -122,9 +140,6 @@ apps/cli/src/locales/en/messages.json apps/desktop/src/locales/en/messages.json apps/web/src/locales/en/messages.json -## Ssh agent temporary co-codeowner -apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-platform-dev @bitwarden/wg-ssh-keys - ## BRE team owns these workflows ## .github/workflows/brew-bump-desktop.yml @bitwarden/dept-bre .github/workflows/deploy-web.yml @bitwarden/dept-bre @@ -140,8 +155,6 @@ apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-platform-dev @bit .github/workflows/release-desktop-beta.yml .github/workflows/release-desktop.yml .github/workflows/release-web.yml -.github/workflows/version-auto-bump.yml -.github/workflows/version-bump.yml ## Docker files have shared ownership ## **/Dockerfile diff --git a/.github/renovate.json b/.github/renovate.json index 0172403f0f1..776c66af68e 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -10,7 +10,7 @@ }, { "matchManagers": ["github-actions"], - "commitMessagePrefix": "[deps] DevOps:" + "commitMessagePrefix": "[deps] BRE:" }, { "matchManagers": ["cargo"], @@ -39,6 +39,10 @@ "groupName": "macOS/iOS bindings", "matchPackageNames": ["core-foundation", "security-framework", "security-framework-sys"] }, + { + "groupName": "zbus", + "matchPackageNames": ["zbus", "zbus_polkit"] + }, { "matchPackageNames": [ "base64-loader", @@ -71,15 +75,12 @@ }, { "matchPackageNames": [ + "@emotion/css", "@webcomponents/custom-elements", "concurrently", "cross-env", "del", - "gulp", - "gulp-if", - "gulp-json-editor", - "gulp-replace", - "gulp-zip", + "lit", "nord", "patch-package", "prettier", @@ -103,6 +104,8 @@ "matchPackageNames": [ "@babel/core", "@babel/preset-env", + "@bitwarden/sdk-internal", + "@electron/fuses", "@electron/notarize", "@electron/rebuild", "@ngtools/webpack", @@ -114,7 +117,7 @@ "@types/node", "@types/node-forge", "@types/node-ipc", - "@yao-pkg", + "@yao-pkg/pkg", "babel-loader", "browserslist", "copy-webpack-plugin", @@ -134,6 +137,7 @@ "tsconfig-paths-webpack-plugin", "type-fest", "typescript", + "typescript-strict-plugin", "webpack", "webpack-cli", "webpack-dev-server", @@ -150,12 +154,13 @@ "@angular/cdk", "@angular/cli", "@angular/common", - "@angular/compiler", "@angular/compiler-cli", + "@angular/compiler", "@angular/core", "@angular/forms", + "@angular/platform-browser-dynamic", + "@angular/platform-browser", "@angular/platform", - "@angular/compiler", "@angular/router", "@compodoc/compodoc", "@ng-select/ng-select", @@ -163,8 +168,11 @@ "@storybook/addon-actions", "@storybook/addon-designs", "@storybook/addon-essentials", + "@storybook/addon-interactions", "@storybook/addon-links", "@storybook/angular", + "@storybook/manager-api", + "@storybook/theming", "@types/react", "autoprefixer", "bootstrap", @@ -187,7 +195,9 @@ "matchPackageNames": [ "@angular-eslint/eslint-plugin", "@angular-eslint/eslint-plugin-template", + "@angular-eslint/schematics", "@angular-eslint/template-parser", + "@angular/elements", "@types/jest", "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", @@ -200,6 +210,7 @@ "eslint-plugin-storybook", "eslint-plugin-tailwindcss", "husky", + "jest-extended", "jest-junit", "jest-mock-extended", "jest-preset-angular", diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 3295dfc17df..73d323851e5 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -5,16 +5,6 @@ ./apps/browser/store/windows/Assets ./bitwarden_license/README.md ./libs/angular/src/directives/cipherListVirtualScroll.directive.ts -./libs/angular/src/scss/webfonts/Open_Sans-italic-700.woff -./libs/angular/src/scss/webfonts/Open_Sans-normal-300.woff -./libs/angular/src/scss/webfonts/Open_Sans-normal-700.woff -./libs/angular/src/scss/webfonts/Open_Sans-italic-300.woff -./libs/angular/src/scss/webfonts/Open_Sans-italic-600.woff -./libs/angular/src/scss/webfonts/Open_Sans-italic-800.woff -./libs/angular/src/scss/webfonts/Open_Sans-italic-400.woff -./libs/angular/src/scss/webfonts/Open_Sans-normal-600.woff -./libs/angular/src/scss/webfonts/Open_Sans-normal-800.woff -./libs/angular/src/scss/webfonts/Open_Sans-normal-400.woff ./libs/admin-console/README.md ./libs/auth/README.md ./libs/billing/README.md @@ -47,7 +37,6 @@ ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts ./apps/browser/src/models/browserComponentState.ts -./apps/browser/src/models/browserSendComponentState.ts ./apps/browser/src/models/browserGroupingsComponentState.ts ./apps/browser/src/models/biometricErrors.ts ./apps/browser/src/browser/safariApp.ts diff --git a/.github/workflows/brew-bump-desktop.yml b/.github/workflows/brew-bump-desktop.yml deleted file mode 100644 index 1b3c99128bf..00000000000 --- a/.github/workflows/brew-bump-desktop.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Bump Desktop Cask - -on: - push: - tags: - - desktop-v** - workflow_dispatch: - -defaults: - run: - shell: bash - -jobs: - update-desktop-cask: - name: Update Bitwarden Desktop Cask - runs-on: macos-13 - steps: - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "brew-bump-workflow-pat" - - - name: Update Homebrew cask - uses: macauley/action-homebrew-bump-cask@445c42390d790569d938f9068d01af39ca030feb # v1.0.0 - with: - # Required, custom GitHub access token with the 'public_repo' and 'workflow' scopes - token: ${{ steps.retrieve-secrets.outputs.brew-bump-workflow-pat }} - org: bitwarden - tap: Homebrew/homebrew-cask - cask: bitwarden - tag: ${{ github.ref }} - revision: ${{ github.sha }} - force: true - dryrun: true diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 42d012d5a98..64cbaa0c7f1 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -27,7 +27,11 @@ on: workflow_call: inputs: {} workflow_dispatch: - inputs: {} + inputs: + sdk_branch: + description: "Custom SDK branch" + required: false + type: string defaults: run: @@ -110,8 +114,8 @@ jobs: fi - build: - name: Build + build-source: + name: Build browser source runs-on: ubuntu-22.04 needs: - setup @@ -123,7 +127,7 @@ jobs: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -164,74 +168,109 @@ jobs: zip -r browser-source.zip browser-source - - name: NPM setup - run: npm ci - working-directory: browser-source/ - - - name: Build - run: npm run dist - working-directory: browser-source/apps/browser - - - name: Build Manifest v3 - run: npm run dist:mv3 - working-directory: browser-source/apps/browser - - - name: Upload Opera artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Upload browser source + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: dist-opera-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-opera.zip + name: browser-source-${{ env._BUILD_NUMBER }}.zip + path: browser-source.zip if-no-files-found: error - - name: Upload Opera MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: DO-NOT-USE-FOR-PROD-dist-opera-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-opera-mv3.zip - if-no-files-found: error - - name: Upload Chrome MV3 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + build: + name: Build + runs-on: ubuntu-22.04 + needs: + - setup + - locales-test + - build-source + env: + _BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }} + _NODE_VERSION: ${{ needs.setup.outputs.node_version }} + strategy: + matrix: + include: + - name: "chrome" + npm_command: "dist:chrome" + archive_name: "dist-chrome.zip" + artifact_name: "dist-chrome-MV3" + - name: "edge" + npm_command: "dist:edge" + archive_name: "dist-edge.zip" + artifact_name: "dist-edge-MV3" + - name: "firefox" + npm_command: "dist:firefox" + archive_name: "dist-firefox.zip" + artifact_name: "dist-firefox" + - name: "firefox-mv3" + npm_command: "dist:firefox:mv3" + archive_name: "dist-firefox.zip" + artifact_name: "DO-NOT-USE-FOR-PROD-dist-firefox-MV3" + - name: "opera" + npm_command: "dist:opera" + archive_name: "dist-opera.zip" + artifact_name: "dist-opera-MV3" + steps: + - name: Check out repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-chrome-mv3.zip - if-no-files-found: error + ref: ${{ github.event.pull_request.head.sha }} - - name: Upload Firefox artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Set up Node + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: - name: dist-firefox-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-firefox.zip - if-no-files-found: error + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + node-version: ${{ env._NODE_VERSION }} - - name: Upload Firefox MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: DO-NOT-USE-FOR-PROD-dist-firefox-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-firefox-mv3.zip - if-no-files-found: error + - name: Print environment + run: | + node --version + npm --version - - name: Upload Edge artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Download browser source + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - name: dist-edge-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-edge.zip - if-no-files-found: error + name: browser-source-${{ env._BUILD_NUMBER }}.zip + + - name: Unzip browser source artifact + run: | + unzip browser-source.zip + rm browser-source.zip + + - name: NPM setup + run: npm ci + working-directory: browser-source/ - - name: Upload Edge MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Download SDK artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main with: - name: DO-NOT-USE-FOR-PROD-dist-edge-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-edge-mv3.zip - if-no-files-found: error + 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: browser-source/ + run: npm link ../sdk-internal - - name: Upload browser source - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - name: Build extension + run: npm run ${{ matrix.npm_command }} + working-directory: browser-source/apps/browser + + - name: Upload extension artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: browser-source-${{ env._BUILD_NUMBER }}.zip - path: browser-source.zip + name: ${{ matrix.artifact_name }}-${{ env._BUILD_NUMBER }}.zip + path: browser-source/apps/browser/dist/${{ matrix.archive_name }} if-no-files-found: error + build-safari: name: Build Safari runs-on: macos-13 @@ -331,6 +370,25 @@ jobs: 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: | + npm link ../sdk-internal + - name: Build Safari extension run: npm run dist:safari working-directory: apps/browser @@ -343,7 +401,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/dist-safari.zip @@ -386,6 +444,7 @@ jobs: upload_sources: true upload_translations: false + check-failures: name: Check for failures if: always() @@ -393,6 +452,7 @@ jobs: needs: - setup - locales-test + - build-source - build - build-safari - crowdin-push diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index ac39ab2608b..02432e0a5f4 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -28,7 +28,11 @@ on: - '.github/workflows/build-cli.yml' - 'bitwarden_license/bit-cli/**' workflow_dispatch: - inputs: {} + inputs: + sdk_branch: + description: "Custom SDK branch" + required: false + type: string defaults: run: @@ -68,7 +72,7 @@ jobs: echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT cli: - name: "${{ matrix.os.base }} - ${{ matrix.license_type.readable }}" + name: CLI ${{ matrix.os.base }} - ${{ matrix.license_type.readable }} strategy: matrix: os: @@ -112,6 +116,26 @@ jobs: 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: Build & Package Unix run: npm run dist:${{ matrix.license_type.build_prefix }}:${{ env.SHORT_RUNNER_OS }} --quiet @@ -139,21 +163,21 @@ jobs: matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt - name: Upload unix zip asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload unix checksum asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt if-no-files-found: error cli-windows: - name: "windows - ${{ matrix.license_type.readable }}" + name: Windows - ${{ matrix.license_type.readable }} strategy: matrix: license_type: @@ -247,6 +271,26 @@ jobs: 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: Build & Package Windows run: npm run dist:${{ matrix.license_type.build_prefix }}:win --quiet @@ -280,14 +324,14 @@ jobs: -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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip 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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.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 @@ -295,7 +339,7 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg @@ -306,7 +350,7 @@ jobs: - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -377,14 +421,14 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap 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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 221c998247f..b27d1486bd2 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -26,7 +26,11 @@ on: - '!*.txt' - '.github/workflows/build-desktop.yml' workflow_dispatch: - inputs: {} + inputs: + sdk_branch: + description: "Custom SDK branch" + required: false + type: string defaults: run: @@ -166,7 +170,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools + sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder - name: Set up Snap run: sudo snap install snapcraft --classic @@ -182,8 +186,28 @@ jobs: 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -208,47 +232,60 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml if-no-files-found: error + - name: Build flatpak + working-directory: apps/desktop + run: | + sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + sudo npm run pack:lin:flatpak + + - name: Upload flatpak artifact + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: com.bitwarden.desktop.flatpak + path: apps/desktop/dist/com.bitwarden.desktop.flatpak + if-no-files-found: error + windows: name: Windows Build @@ -315,8 +352,28 @@ jobs: 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -371,91 +428,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -504,14 +561,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -603,8 +660,28 @@ jobs: 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -672,14 +749,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -771,8 +848,28 @@ jobs: 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -821,28 +918,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -893,14 +990,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -999,8 +1096,28 @@ jobs: 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -1049,7 +1166,7 @@ jobs: run: npm run pack:mac:mas - name: Upload .pkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1076,9 +1193,11 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop') - uses: slackapi/slack-github-action@37ebaef184d7626c5f204ab8d3baff4262dd30f0 # v1.27.0 + uses: slackapi/slack-github-action@485a9d42d3a73031f12ec201c457e2162c45d02d # v2.0.0 with: channel-id: C074F5UESQ0 + method: chat.postMessage + token: ${{ steps.retrieve-slack-secret.outputs.slack-bot-token }} payload: | { "blocks": [ @@ -1092,7 +1211,6 @@ jobs: ] } env: - SLACK_BOT_TOKEN: ${{ steps.retrieve-slack-secret.outputs.slack-bot-token }} BUILD_NUMBER: ${{ needs.setup.outputs.build_number }} @@ -1135,14 +1253,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1234,8 +1352,28 @@ jobs: 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -1287,7 +1425,7 @@ jobs: zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip @@ -1296,7 +1434,7 @@ jobs: crowdin-push: name: Crowdin Push - if: github.ref == 'refs/heads/main' + if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' needs: - linux - windows diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index ba4f2599f37..73ae0e14962 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -32,6 +32,10 @@ on: custom_tag_extension: description: "Custom image tag extension" required: false + sdk_branch: + description: "Custom SDK branch" + required: false + type: string env: _AZ_REGISTRY: bitwardenprod.azurecr.io @@ -116,7 +120,6 @@ jobs: whoami node --version npm --version - gulp --version docker --version echo "GitHub ref: $GITHUB_REF" echo "GitHub event: $GITHUB_EVENT" @@ -124,6 +127,26 @@ jobs: - name: Install dependencies run: npm ci + - 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: Add Git metadata to build version working-directory: apps/web if: matrix.git_metadata @@ -141,7 +164,7 @@ jobs: run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: web-${{ env._VERSION }}-${{ matrix.name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip @@ -151,6 +174,9 @@ jobs: build-containers: name: Build Docker images runs-on: ubuntu-22.04 + permissions: + security-events: write + id-token: write needs: - setup - build-artifacts @@ -216,7 +242,7 @@ jobs: - name: Generate Docker image tag id: tag run: | - if [[ $(grep "pull" <<< "${GITHUB_REF}") ]]; then + if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s#/#-#g") else IMAGE_TAG=$(echo "${GITHUB_REF_NAME}" | sed "s#/#-#g") @@ -247,7 +273,8 @@ jobs: run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - name: Build Docker image - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + id: build-docker + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: context: apps/web file: apps/web/Dockerfile @@ -256,11 +283,40 @@ jobs: tags: ${{ steps.image-name.outputs.name }} secrets: | "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" + + - name: Install Cosign + if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 + + - name: Sign image with Cosign + if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' + env: + DIGEST: ${{ steps.build-docker.outputs.digest }} + TAGS: ${{ steps.image-name.outputs.name }} + run: | + IFS="," read -a tags <<< "${TAGS}" + images="" + for tag in "${tags[@]}"; do + images+="${tag}@${DIGEST} " + done + cosign sign --yes ${images} + + - name: Scan Docker image + id: container-scan + uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 + with: + image: ${{ steps.image-name.outputs.name }} + fail-build: false + output-format: sarif + + - name: Upload Grype results to GitHub + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + with: + sarif_file: ${{ steps.container-scan.outputs.sarif }} - name: Log out of Docker run: docker logout - 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 0efd9d22f17..a5ebd363f63 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -43,7 +43,7 @@ jobs: - name: Cache NPM id: npm-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: "~/.npm" key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }} @@ -56,7 +56,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@dd2eecb9bef44f54774581f4163b0327fd8cf607 # v11.16.3 + uses: chromaui/action@64a9c0ca3bfb724389b0d536e544f56b7b5ff5b3 # v11.20.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 540da77b554..027a2f11e55 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -9,7 +9,7 @@ on: jobs: crowdin-sync: name: Autosync - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -21,8 +21,17 @@ jobs: - app_name: web crowdin_project_id: "308189" steps: + - name: Generate GH App token + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + token: ${{ steps.app-token.outputs.token }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -39,7 +48,7 @@ jobs: - name: Download translations uses: bitwarden/gh-actions/crowdin@main env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_PROJECT_ID: ${{ matrix.crowdin_project_id }} with: diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 5cc4eb90861..9b890491282 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -63,14 +63,14 @@ jobs: runs-on: ubuntu-22.04 outputs: environment: ${{ steps.config.outputs.environment }} - environment-url: ${{ steps.config.outputs.environment-url }} - environment-name: ${{ steps.config.outputs.environment-name }} - environment-artifact: ${{ steps.config.outputs.environment-artifact }} - azure-login-creds: ${{ steps.config.outputs.azure-login-creds }} - retrieve-secrets-keyvault: ${{ steps.config.outputs.retrieve-secrets-keyvault }} - sync-utility: ${{ steps.config.outputs.sync-utility }} - sync-delete-destination-files: ${{ steps.config.outputs.sync-delete-destination-files }} - slack-channel-name: ${{ steps.config.outputs.slack-channel-name }} + environment_url: ${{ steps.config.outputs.environment_url }} + environment_name: ${{ steps.config.outputs.environment_name }} + environment_artifact: ${{ steps.config.outputs.environment_artifact }} + azure_login_creds: ${{ steps.config.outputs.azure_login_creds }} + retrive_secrets_keyvault: ${{ steps.config.outputs.retrive_secrets_keyvault }} + sync_utility: ${{ steps.config.outputs.sync_utility }} + sync_delete_destination_files: ${{ steps.config.outputs.sync_delete_destination_files }} + slack_channel_name: ${{ steps.config.outputs.slack_channel_name }} steps: - name: Configure id: config @@ -81,48 +81,48 @@ jobs: case ${{ inputs.environment }} in "USQA") - echo "azure-login-creds=AZURE_KV_US_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=bw-webvault-rlktusqa-kv" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-QA.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - US QA Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-qa" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_US_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=bw-webvault-rlktusqa-kv" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-QA.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - US QA Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-qa" >> $GITHUB_OUTPUT ;; "EUQA") - echo "azure-login-creds=AZURE_KV_EU_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=webvaulteu-westeurope-qa" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-euqa.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - EU QA Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-qa" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_EU_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=webvaulteu-westeurope-qa" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-euqa.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - EU QA Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-qa" >> $GITHUB_OUTPUT ;; "USPROD") - echo "azure-login-creds=AZURE_KV_US_PROD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=bw-webvault-klrt-kv" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-COMMERCIAL.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - US Production Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.bitwarden.com" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-prd" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_US_PROD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=bw-webvault-klrt-kv" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-COMMERCIAL.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - US Production Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.bitwarden.com" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-prd" >> $GITHUB_OUTPUT ;; "EUPROD") - echo "azure-login-creds=AZURE_KV_EU_PRD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=webvault-westeurope-prod" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-euprd.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - EU Production Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.bitwarden.eu" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-prd" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_EU_PRD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=webvault-westeurope-prod" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-euprd.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - EU Production Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.bitwarden.eu" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-prd" >> $GITHUB_OUTPUT ;; "USDEV") - echo "azure-login-creds=AZURE_KV_US_DEV_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=webvault-eastus-dev" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-usdev.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - US Development Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-dev" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_US_DEV_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=webvault-eastus-dev" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-usdev.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - US Development Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-dev" >> $GITHUB_OUTPUT ;; esac # Set the sync utility to use for deployment to the environment (az-sync or azcopy) - echo "sync-utility=azcopy" >> $GITHUB_OUTPUT + echo "sync_utility=azcopy" >> $GITHUB_OUTPUT - name: Environment Protection env: @@ -168,10 +168,10 @@ jobs: fi approval: - name: Approval for Deployment to ${{ needs.setup.outputs.environment-name }} + name: Approval for Deployment to ${{ needs.setup.outputs.environment_name }} needs: setup runs-on: ubuntu-22.04 - environment: ${{ needs.setup.outputs.environment-name }} + environment: ${{ needs.setup.outputs.environment_name }} steps: - name: Success Code run: exit 0 @@ -181,9 +181,9 @@ jobs: runs-on: ubuntu-22.04 needs: setup env: - _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} + _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment_artifact }} outputs: - artifact-build-commit: ${{ steps.set-artifact-commit.outputs.commit }} + artifact_build_commit: ${{ steps.set-artifact-commit.outputs.commit }} steps: - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' if: ${{ inputs.build-web-run-id }} @@ -242,7 +242,7 @@ jobs: run: | # If run-id was used, get the commit from the download-latest-artifacts-run-id step if [ "${{ inputs.build-web-run-id }}" ]; then - echo "commit=${{ steps.download-latest-artifacts-run-id.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + echo "commit=${{ steps.download-latest-artifacts-run-id.outputs.artifact_build_commit }}" >> $GITHUB_OUTPUT elif [ "${{ steps.download-latest-artifacts.outcome }}" == "failure" ]; then # If the download-latest-artifacts step failed, query the GH API to get the commit SHA of the artifact that was just built with trigger-build-web. @@ -251,7 +251,7 @@ jobs: else # Set the commit to the output of step download-latest-artifacts. - echo "commit=${{ steps.download-latest-artifacts.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + echo "commit=${{ steps.download-latest-artifacts.outputs.artifact_build_commit }}" >> $GITHUB_OUTPUT fi notify-start: @@ -266,15 +266,16 @@ jobs: channel_id: ${{ steps.slack-message.outputs.channel_id }} ts: ${{ steps.slack-message.outputs.ts }} steps: - - uses: bitwarden/gh-actions/report-deployment-status-to-slack@main + - name: Notify Slack with start message + uses: bitwarden/gh-actions/report-deployment-status-to-slack@main id: slack-message with: project: Clients - environment: ${{ needs.setup.outputs.environment-name }} + environment: ${{ needs.setup.outputs.environment_name }} tag: ${{ inputs.branch-or-tag }} - slack-channel: ${{ needs.setup.outputs.slack-channel-name }} + slack-channel: ${{ needs.setup.outputs.slack_channel_name }} event: 'start' - commit-sha: ${{ needs.artifact-check.outputs.artifact-build-commit }} + commit-sha: ${{ needs.artifact-check.outputs.artifact_build_commit }} url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }} AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -286,7 +287,7 @@ jobs: - name: Display commit SHA run: | REPO_URL="https://github.com/bitwarden/clients/commit" - COMMIT_SHA="${{ needs.artifact-check.outputs.artifact-build-commit }}" + COMMIT_SHA="${{ needs.artifact-check.outputs.artifact_build_commit }}" echo ":steam_locomotive: View [commit]($REPO_URL/$COMMIT_SHA)" >> $GITHUB_STEP_SUMMARY azure-deploy: @@ -298,9 +299,9 @@ jobs: runs-on: ubuntu-22.04 env: _ENVIRONMENT: ${{ needs.setup.outputs.environment }} - _ENVIRONMENT_URL: ${{ needs.setup.outputs.environment-url }} - _ENVIRONMENT_NAME: ${{ needs.setup.outputs.environment-name }} - _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} + _ENVIRONMENT_URL: ${{ needs.setup.outputs.environment_url }} + _ENVIRONMENT_NAME: ${{ needs.setup.outputs.environment_name }} + _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment_artifact }} steps: - name: Create GitHub deployment uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 @@ -308,31 +309,31 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' - environment-url: ${{ env._ENVIRONMENT_URL }} + environment_url: ${{ env._ENVIRONMENT_URL }} environment: ${{ env._ENVIRONMENT_NAME }} task: 'deploy' description: 'Deployment from branch/tag: ${{ inputs.branch-or-tag }}' - ref: ${{ needs.artifact-check.outputs.artifact-build-commit }} + ref: ${{ needs.artifact-check.outputs.artifact_build_commit }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: - creds: ${{ secrets[needs.setup.outputs.azure-login-creds] }} + creds: ${{ secrets[needs.setup.outputs.azure_login_creds] }} - name: Retrieve Storage Account connection string for az sync - if: ${{ needs.setup.outputs.sync-utility == 'az-sync' }} + if: ${{ needs.setup.outputs.sync_utility == 'az-sync' }} id: retrieve-secrets-az-sync uses: bitwarden/gh-actions/get-keyvault-secrets@main with: - keyvault: ${{ needs.setup.outputs.retrieve-secrets-keyvault }} + keyvault: ${{ needs.setup.outputs.retrive_secrets_keyvault }} secrets: "sa-bitwarden-web-vault-dev-key-temp" - name: Retrieve Storage Account name and SPN credentials for azcopy - if: ${{ needs.setup.outputs.sync-utility == 'azcopy' }} + if: ${{ needs.setup.outputs.sync_utility == 'azcopy' }} id: retrieve-secrets-azcopy uses: bitwarden/gh-actions/get-keyvault-secrets@main with: - keyvault: ${{ needs.setup.outputs.retrieve-secrets-keyvault }} + keyvault: ${{ needs.setup.outputs.retrive_secrets_keyvault }} secrets: "sa-bitwarden-web-vault-name,sp-bitwarden-web-vault-password,sp-bitwarden-web-vault-appid,sp-bitwarden-web-vault-tenant" - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' @@ -362,7 +363,7 @@ jobs: run: unzip ${{ env._ENVIRONMENT_ARTIFACT }} - name: Sync to Azure Storage Account using az storage blob sync - if: ${{ needs.setup.outputs.sync-utility == 'az-sync' }} + if: ${{ needs.setup.outputs.sync_utility == 'az-sync' }} working-directory: apps/web run: | az storage blob sync \ @@ -372,7 +373,7 @@ jobs: --delete-destination=${{ inputs.force-delete-destination }} - name: Sync to Azure Storage Account using azcopy - if: ${{ needs.setup.outputs.sync-utility == 'azcopy' }} + if: ${{ needs.setup.outputs.sync_utility == 'azcopy' }} working-directory: apps/web env: AZCOPY_AUTO_LOGIN_TYPE: SPN @@ -396,7 +397,7 @@ jobs: uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' - environment-url: ${{ env._ENVIRONMENT_URL }} + environment_url: ${{ env._ENVIRONMENT_URL }} state: 'success' deployment-id: ${{ steps.deployment.outputs.deployment_id }} @@ -405,7 +406,7 @@ jobs: uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' - environment-url: ${{ env._ENVIRONMENT_URL }} + environment_url: ${{ env._ENVIRONMENT_URL }} state: 'failure' deployment-id: ${{ steps.deployment.outputs.deployment_id }} @@ -419,14 +420,15 @@ jobs: - azure-deploy - artifact-check steps: - - uses: bitwarden/gh-actions/report-deployment-status-to-slack@main + - name: Notify Slack with result + uses: bitwarden/gh-actions/report-deployment-status-to-slack@main with: project: Clients - environment: ${{ needs.setup.outputs.environment-name }} + environment: ${{ needs.setup.outputs.environment_name }} tag: ${{ inputs.branch-or-tag }} slack-channel: ${{ needs.notify-start.outputs.channel_id }} event: ${{ needs.azure-deploy.result }} url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }} - commit-sha: ${{ needs.artifact-check.outputs.artifact-build-commit }} + commit-sha: ${{ needs.artifact-check.outputs.artifact_build_commit }} update-ts: ${{ needs.notify-start.outputs.ts }} AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 561cd9af0c8..867de3844e7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,6 +36,7 @@ jobs: ! -path "./.github/*" \ ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ + ! -path "./apps/desktop/macos/*" \ > tmp.txt diff <(sort .github/whitelist-capital-letters.txt) <(sort tmp.txt) @@ -53,7 +54,39 @@ jobs: cache-dependency-path: '**/package-lock.json' node-version: ${{ steps.retrieve-node-version.outputs.node_version }} + - name: Install Node dependencies + run: npm ci + + - name: Lint unowned dependencies + run: npm run lint:dep-ownership + - name: Run linter - run: | - npm ci - npm run lint + run: npm run lint + + rust: + name: Run Rust lint on ${{ matrix.os }} + runs-on: ${{ matrix.os || 'ubuntu-24.04' }} + + strategy: + matrix: + os: + - ubuntu-24.04 + - macos-14 + - windows-2022 + + steps: + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Check Rust version + run: rustup --version + + - name: Run cargo fmt + working-directory: ./apps/desktop/desktop_native + run: cargo fmt --check + + - name: Run Clippy + working-directory: ./apps/desktop/desktop_native + run: cargo clippy --all-features --tests + env: + RUSTFLAGS: "-D warnings" diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 0a561306797..ff85a30d3f6 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -43,8 +43,8 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version-output.outputs.version }} - deployment-id: ${{ steps.deployment.outputs.deployment_id }} + release_version: ${{ steps.version-output.outputs.version }} + deployment_id: ${{ steps.deployment.outputs.deployment_id }} defaults: run: working-directory: . @@ -88,7 +88,7 @@ jobs: needs: setup if: inputs.snap_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -125,7 +125,7 @@ jobs: needs: setup if: inputs.choco_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -165,7 +165,7 @@ jobs: needs: setup if: inputs.npm_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -222,7 +222,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -230,4 +230,4 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 5ef378ad439..69ccd841065 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -39,10 +39,10 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} - release-channel: ${{ steps.release-channel.outputs.channel }} - tag-name: ${{ steps.version.outputs.tag_name }} - deployment-id: ${{ steps.deployment.outputs.deployment_id }} + release_version: ${{ steps.version.outputs.version }} + release_channel: ${{ steps.release_channel.outputs.channel }} + tag_name: ${{ steps.version.outputs.tag_name }} + deployment_id: ${{ steps.deployment.outputs.deployment_id }} steps: - name: Branch check if: ${{ inputs.publish_type != 'Dry Run' }} @@ -76,7 +76,7 @@ jobs: fi - name: Get Version Channel - id: release-channel + id: release_channel run: | case "${{ steps.version.outputs.version }}" in *"alpha"*) @@ -100,7 +100,7 @@ jobs: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' environment: 'Desktop - Production' - description: 'Deployment ${{ steps.version.outputs.version }} to channel ${{ steps.release-channel.outputs.channel }} from branch ${{ github.ref_name }}' + description: 'Deployment ${{ steps.version.outputs.version }} to channel ${{ steps.release_channel.outputs.channel }} from branch ${{ github.ref_name }}' task: release electron-blob: @@ -108,8 +108,8 @@ jobs: runs-on: ubuntu-22.04 needs: setup env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -136,7 +136,7 @@ jobs: - name: Set staged rollout percentage env: - RELEASE_CHANNEL: ${{ needs.setup.outputs.release-channel }} + RELEASE_CHANNEL: ${{ needs.setup.outputs.release_channel }} ROLLOUT_PCT: ${{ inputs.rollout_percentage }} run: | echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml @@ -163,7 +163,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -171,7 +171,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} snap: name: Deploy Snap @@ -179,8 +179,8 @@ jobs: needs: setup if: inputs.snap_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -223,8 +223,8 @@ jobs: needs: setup if: inputs.choco_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -284,7 +284,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -292,4 +292,4 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 4c3321c015d..7e8722dc79f 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -23,7 +23,7 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} + release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -118,10 +118,10 @@ jobs: - name: Rename build artifacts env: - PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} run: | mv browser-source.zip browser-source-$PACKAGE_VERSION.zip - mv dist-chrome-mv3.zip dist-chrome-$PACKAGE_VERSION.zip + mv dist-chrome.zip dist-chrome-$PACKAGE_VERSION.zip mv dist-opera.zip dist-opera-$PACKAGE_VERSION.zip mv dist-firefox.zip dist-firefox-$PACKAGE_VERSION.zip mv dist-edge.zip dist-edge-$PACKAGE_VERSION.zip @@ -130,14 +130,14 @@ jobs: if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 with: - artifacts: 'browser-source-${{ needs.setup.outputs.release-version }}.zip, - dist-chrome-${{ needs.setup.outputs.release-version }}.zip, - dist-opera-${{ needs.setup.outputs.release-version }}.zip, - dist-firefox-${{ needs.setup.outputs.release-version }}.zip, - dist-edge-${{ needs.setup.outputs.release-version }}.zip' + artifacts: 'browser-source-${{ needs.setup.outputs.release_version }}.zip, + dist-chrome-${{ needs.setup.outputs.release_version }}.zip, + dist-opera-${{ needs.setup.outputs.release_version }}.zip, + dist-firefox-${{ needs.setup.outputs.release_version }}.zip, + dist-edge-${{ needs.setup.outputs.release_version }}.zip' commit: ${{ github.sha }} - tag: "browser-v${{ needs.setup.outputs.release-version }}" - name: "Browser v${{ needs.setup.outputs.release-version }}" + tag: "browser-v${{ needs.setup.outputs.release_version }}" + name: "Browser v${{ needs.setup.outputs.release_version }}" body: "" token: ${{ secrets.GITHUB_TOKEN }} draft: true diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 05c53f9752d..d16cd744d7d 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -23,7 +23,7 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} + release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: ${{ inputs.release_type }} project-type: ts @@ -75,7 +75,7 @@ jobs: if: ${{ inputs.release_type != 'Dry Run' }} uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: - PKG_VERSION: ${{ needs.setup.outputs.release-version }} + 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, diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index c1646997201..08174dc552e 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -16,9 +16,9 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} - release-channel: ${{ steps.release-channel.outputs.channel }} - branch-name: ${{ steps.branch.outputs.branch-name }} + release_version: ${{ steps.version.outputs.version }} + release_channel: ${{ steps.release_channel.outputs.channel }} + branch_name: ${{ steps.branch.outputs.branch_name }} build_number: ${{ steps.increment-version.outputs.build_number }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} steps: @@ -47,7 +47,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: 'Initial Release' project-type: ts @@ -63,7 +63,7 @@ jobs: echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT - name: Get Version Channel - id: release-channel + id: release_channel run: | case "${{ steps.version.outputs.version }}" in *"alpha"*) @@ -102,7 +102,7 @@ jobs: git push -u origin $branch_name - echo "branch-name=$branch_name" >> $GITHUB_OUTPUT + echo "branch_name=$branch_name" >> $GITHUB_OUTPUT - name: Get Node Version id: retrieve-node-version @@ -116,7 +116,7 @@ jobs: runs-on: ubuntu-22.04 needs: setup env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -126,7 +126,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -138,7 +138,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm + sudo apt-get -y install pkg-config libxss-dev rpm - name: Set up Snap run: sudo snap install snapcraft --classic @@ -158,45 +158,45 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: ${{ needs.setup.outputs.release-channel }}-linux.yml - path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-linux.yml + name: ${{ needs.setup.outputs.release_channel }}-linux.yml + path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml if-no-files-found: error @@ -209,14 +209,14 @@ jobs: shell: pwsh working-directory: apps/desktop env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -299,94 +299,94 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: ${{ needs.setup.outputs.release-channel }}.yml - path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release-channel }}.yml + name: ${{ needs.setup.outputs.release_channel }}.yml + path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml if-no-files-found: error @@ -395,7 +395,7 @@ jobs: runs-on: macos-13 needs: setup env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -405,7 +405,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -426,14 +426,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -529,7 +529,7 @@ jobs: - setup - macos-build env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -539,7 +539,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -560,14 +560,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -707,31 +707,31 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: ${{ needs.setup.outputs.release-channel }}-mac.yml - path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-mac.yml + name: ${{ needs.setup.outputs.release_channel }}-mac.yml + path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml if-no-files-found: error @@ -742,7 +742,7 @@ jobs: - setup - macos-build env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -752,7 +752,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -773,14 +773,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + 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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -915,7 +915,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Upload .pkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -939,7 +939,7 @@ jobs: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' environment: 'Desktop - Beta' - description: 'Deployment ${{ needs.setup.outputs.release-version }} to channel ${{ needs.setup.outputs.release-channel }} from branch ${{ needs.setup.outputs.branch-name }}' + description: 'Deployment ${{ needs.setup.outputs.release_version }} to channel ${{ needs.setup.outputs.release_channel }} from branch ${{ needs.setup.outputs.branch_name }}' task: release - name: Login to Azure @@ -963,7 +963,7 @@ jobs: - name: Rename .pkg to .pkg.archive env: - PKG_VERSION: ${{ needs.setup.outputs.release-version }} + PKG_VERSION: ${{ needs.setup.outputs.release_version }} working-directory: apps/desktop/artifacts run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive @@ -1020,5 +1020,5 @@ jobs: git config --global url."https://".insteadOf ssh:// - name: Remove branch env: - BRANCH: ${{ needs.setup.outputs.branch-name }} + BRANCH: ${{ needs.setup.outputs.branch_name }} run: git push origin --delete $BRANCH diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index d9394347f60..ba934235b44 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -22,8 +22,8 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} - release-channel: ${{ steps.release-channel.outputs.channel }} + release_version: ${{ steps.version.outputs.version }} + release_channel: ${{ steps.release_channel.outputs.channel }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: ${{ inputs.release_type }} project-type: ts @@ -49,7 +49,7 @@ jobs: monorepo-project: desktop - name: Get Version Channel - id: release-channel + id: release_channel run: | case "${{ steps.version.outputs.version }}" in *"alpha"*) @@ -97,10 +97,10 @@ jobs: - name: Create Release uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 - if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} + if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} env: PKG_VERSION: ${{ steps.version.outputs.version }} - RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} + RELEASE_CHANNEL: ${{ steps.release_channel.outputs.channel }} with: artifacts: "apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-amd64.deb, apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm, diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index 21de47f13ba..ac2733e765b 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -44,7 +44,6 @@ jobs: runs-on: ubuntu-24.04 outputs: branch: ${{ steps.set-branch.outputs.branch }} - token: ${{ steps.app-token.outputs.token }} steps: - name: Set branch id: set-branch @@ -59,13 +58,6 @@ jobs: echo "branch=$BRANCH" >> $GITHUB_OUTPUT - - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 - id: app-token - with: - app-id: ${{ secrets.BW_GHAPP_ID }} - private-key: ${{ secrets.BW_GHAPP_KEY }} - cut_branch: name: Cut branch @@ -73,11 +65,18 @@ jobs: needs: setup runs-on: ubuntu-24.04 steps: + - name: Generate GH App token + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Check out target ref uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.target_ref }} - token: ${{ needs.setup.outputs.token }} + token: ${{ steps.app-token.outputs.token }} - name: Check if ${{ needs.setup.outputs.branch }} branch exists env: @@ -115,11 +114,18 @@ jobs: with: version: ${{ inputs.version_number_override }} + - name: Generate GH App token + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Check out branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: main - token: ${{ needs.setup.outputs.token }} + token: ${{ steps.app-token.outputs.token }} - name: Configure Git run: | @@ -445,11 +451,19 @@ jobs: - bump_version - setup steps: + - name: Generate GH App token + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Check out main branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: + fetch-depth: 0 ref: main - token: ${{ needs.setup.outputs.token }} + token: ${{ steps.app-token.outputs.token }} - name: Configure Git run: | @@ -477,6 +491,7 @@ jobs: git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT git push -u origin $destination_branch fi + } # Cherry-pick from 'main' into 'rc' cherry_pick browser main rc diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index bf17459c21c..a09e8137b65 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -31,7 +31,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36 + uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: cx_result.sarif @@ -66,10 +66,9 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@383f7e52eae3ab0510c3cb0e7d9d150bbaeab838 # v3.1.0 + uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: args: > -Dsonar.organization=${{ github.repository_owner }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c324cb8748..72bc3594beb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -98,14 +98,14 @@ jobs: rust: name: Run Rust tests on ${{ matrix.os }} - runs-on: ${{ matrix.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} permissions: contents: read strategy: matrix: os: - - ubuntu-latest + - ubuntu-22.04 - macos-latest - windows-latest diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index f41261cb39a..ef46dbc867d 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@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.gitignore b/.gitignore index 6dea4b43f16..d0d8edd596c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ npm-debug.log dist build .angular/cache +.flatpak +.flatpak-repo +.flatpak-builder # Testing coverage diff --git a/.storybook/main.ts b/.storybook/main.ts index 454da4377dc..b48a86ba2b2 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -57,6 +57,7 @@ const config: StorybookConfig = { return config; }, docs: {}, + staticDirs: ["../apps/web/src/images"], }; export default config; diff --git a/.vscode/settings.json b/.vscode/settings.json index 3a70af3481d..6b31121e17b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ "**/_locales/[^e]*/messages.json": true, "**/_locales/*[^n]/messages.json": true }, - "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"] + "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"], + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/README.md b/README.md index da31a2d4893..22c8d329f1c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ Please refer to the [Clients section](https://contributing.bitwarden.com/getting ## Related projects: - [bitwarden/server](https://github.com/bitwarden/server): The core infrastructure backend (API, database, Docker, etc). -- [bitwarden/mobile](https://github.com/bitwarden/mobile): The mobile app vault (iOS and Android). +- [bitwarden/ios](https://github.com/bitwarden/ios): Bitwarden mobile app for iOS. +- [bitwarden/android](https://github.com/bitwarden/android): Bitwarden mobile app for Android. - [bitwarden/directory-connector](https://github.com/bitwarden/directory-connector): A tool for syncing a directory (AD, LDAP, Azure, G Suite, Okta) to an organization. # We're Hiring! diff --git a/apps/browser/config/base.json b/apps/browser/config/base.json index 91d48309240..02bdc5d22af 100644 --- a/apps/browser/config/base.json +++ b/apps/browser/config/base.json @@ -1,7 +1,6 @@ { "devFlags": {}, "flags": { - "showPasswordless": true, "accountSwitching": false, "sdk": true } diff --git a/apps/browser/config/development.json b/apps/browser/config/development.json index cc28e15f38b..042a98c2c39 100644 --- a/apps/browser/config/development.json +++ b/apps/browser/config/development.json @@ -6,7 +6,6 @@ "skipWelcomeOnInstall": true }, "flags": { - "showPasswordless": true, "accountSwitching": true } } diff --git a/apps/browser/gulpfile.js b/apps/browser/gulpfile.js deleted file mode 100644 index ed977df4715..00000000000 --- a/apps/browser/gulpfile.js +++ /dev/null @@ -1,233 +0,0 @@ -const child = require("child_process"); -const fs = require("fs"); - -const { rimraf } = require("rimraf"); -const gulp = require("gulp"); -const gulpif = require("gulp-if"); -const jeditor = require("gulp-json-editor"); -const replace = require("gulp-replace"); - -const manifest = require("./src/manifest.json"); -const manifestVersion = parseInt(process.env.MANIFEST_VERSION || manifest.version); - -const paths = { - build: "./build/", - dist: "./dist/", - safari: "./src/safari/", -}; - -function buildString() { - var build = ""; - if (process.env.MANIFEST_VERSION) { - build = `-mv${process.env.MANIFEST_VERSION}`; - } - if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") { - build = `-${process.env.BUILD_NUMBER}`; - } - return build; -} - -function distFileName(browserName, ext) { - return `dist-${browserName}${buildString()}.${ext}`; -} - -async function dist(browserName, manifest) { - const { default: zip } = await import("gulp-zip"); - - return gulp - .src(paths.build + "**/*") - .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_" + browserName))) - .pipe(gulpif("manifest.json", jeditor(manifest))) - .pipe(zip(distFileName(browserName, "zip"))) - .pipe(gulp.dest(paths.dist)); -} - -function distFirefox() { - return dist("firefox", (manifest) => { - if (manifestVersion === 3) { - const backgroundScript = manifest.background.service_worker; - delete manifest.background.service_worker; - manifest.background.scripts = [backgroundScript]; - } - delete manifest.storage; - delete manifest.sandbox; - manifest.optional_permissions = manifest.optional_permissions.filter( - (permission) => permission !== "privacy", - ); - return manifest; - }); -} - -function distOpera() { - return dist("opera", (manifest) => { - delete manifest.applications; - - // Mv3 on Opera does seem to have sidebar support, however it is not working as expected. - // On install, the extension will crash the browser entirely if the sidebar_action key is set. - // We will remove the sidebar_action key for now until opera implements a fix. - if (manifestVersion === 3) { - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - } - - return manifest; - }); -} - -function distChrome() { - return dist("chrome", (manifest) => { - delete manifest.applications; - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - return manifest; - }); -} - -function distEdge() { - return dist("edge", (manifest) => { - delete manifest.applications; - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - return manifest; - }); -} - -function distSafariMas(cb) { - return distSafariApp(cb, "mas"); -} - -function distSafariMasDev(cb) { - return distSafariApp(cb, "masdev"); -} - -function distSafariDmg(cb) { - return distSafariApp(cb, "dmg"); -} - -function distSafariApp(cb, subBuildPath) { - const buildPath = paths.dist + "Safari/" + subBuildPath + "/"; - const builtAppexPath = buildPath + "build/Release/safari.appex"; - const builtAppexFrameworkPath = buildPath + "build/Release/safari.appex/Contents/Frameworks/"; - const entitlementsPath = paths.safari + "safari/safari.entitlements"; - var args = [ - "--verbose", - "--force", - "-o", - "runtime", - "--sign", - "Developer ID Application: 8bit Solutions LLC", - "--entitlements", - entitlementsPath, - ]; - if (subBuildPath !== "dmg") { - args = [ - "--verbose", - "--force", - "--sign", - subBuildPath === "mas" - ? "3rd Party Mac Developer Application: Bitwarden Inc" - : "E7C9978F6FBCE0553429185C405E61F5380BE8EB", - "--entitlements", - entitlementsPath, - ]; - } - - return rimraf([buildPath + "**/*"], { glob: true }) - .then(() => safariCopyAssets(paths.safari + "**/*", buildPath)) - .then(() => safariCopyBuild(paths.build + "**/*", buildPath + "safari/app")) - .then(() => { - const proc = child.spawn("xcodebuild", [ - "-project", - buildPath + "desktop.xcodeproj", - "-alltargets", - "-configuration", - "Release", - ]); - stdOutProc(proc); - return new Promise((resolve) => proc.on("close", resolve)); - }) - .then(async () => { - const libs = fs - .readdirSync(builtAppexFrameworkPath) - .filter((p) => p.endsWith(".dylib")) - .map((p) => builtAppexFrameworkPath + p); - const libPromises = []; - libs.forEach((i) => { - const proc = child.spawn("codesign", args.concat([i])); - stdOutProc(proc); - libPromises.push(new Promise((resolve) => proc.on("close", resolve))); - }); - return Promise.all(libPromises); - }) - .then(() => { - const proc = child.spawn("codesign", args.concat([builtAppexPath])); - stdOutProc(proc); - return new Promise((resolve) => proc.on("close", resolve)); - }) - .then( - () => { - return cb; - }, - () => { - return cb; - }, - ); -} - -function safariCopyAssets(source, dest) { - return new Promise((resolve, reject) => { - gulp - .src(source) - .on("error", reject) - .pipe(gulpif("safari/Info.plist", replace("0.0.1", manifest.version))) - .pipe( - gulpif("safari/Info.plist", replace("0.0.2", process.env.BUILD_NUMBER || manifest.version)), - ) - .pipe(gulpif("desktop.xcodeproj/project.pbxproj", replace("../../../build", "../safari/app"))) - .pipe(gulp.dest(dest)) - .on("end", resolve); - }); -} - -async function safariCopyBuild(source, dest) { - return new Promise((resolve, reject) => { - gulp - .src(source) - .on("error", reject) - .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_safari"))) - .pipe( - gulpif( - "manifest.json", - jeditor((manifest) => { - if (manifestVersion === 3) { - const backgroundScript = manifest.background.service_worker; - delete manifest.background.service_worker; - manifest.background.scripts = [backgroundScript]; - } - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - delete manifest.optional_permissions; - manifest.permissions.push("nativeMessaging"); - return manifest; - }), - ), - ) - .pipe(gulp.dest(dest)) - .on("end", resolve); - }); -} - -function stdOutProc(proc) { - proc.stdout.on("data", (data) => console.log(data.toString())); - proc.stderr.on("data", (data) => console.error(data.toString())); -} - -exports["dist:firefox"] = distFirefox; -exports["dist:chrome"] = distChrome; -exports["dist:opera"] = distOpera; -exports["dist:edge"] = distEdge; -exports["dist:safari"] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg); -exports["dist:safari:mas"] = distSafariMas; -exports["dist:safari:masdev"] = distSafariMasDev; -exports["dist:safari:dmg"] = distSafariDmg; -exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge); diff --git a/apps/browser/package.json b/apps/browser/package.json index 4a749522545..9ad1805362e 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,29 +1,36 @@ { "name": "@bitwarden/browser", - "version": "2024.11.0", + "version": "2025.1.1", "scripts": { - "build": "cross-env MANIFEST_VERSION=3 webpack", - "build:mv2": "webpack", + "build": "npm run build:chrome", + "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 webpack", + "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 webpack", + "build:firefox": "cross-env BROWSER=firefox webpack", + "build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 webpack", + "build:safari": "cross-env BROWSER=safari webpack", "build:watch": "npm run build:watch:chrome", - "build:watch:chrome": "cross-env MANIFEST_VERSION=3 BROWSER=chrome webpack --watch", - "build:watch:firefox": "cross-env MANIFEST_VERSION=3 BROWSER=firefox webpack --watch", - "build:watch:safari": "cross-env MANIFEST_VERSION=3 BROWSER=safari webpack --watch", - "build:watch:mv2": "webpack --watch", - "build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" webpack", - "build:prod:watch": "cross-env NODE_ENV=production webpack --watch", - "dist": "npm run build:prod && gulp dist", - "dist:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && cross-env MANIFEST_VERSION=3 gulp dist", - "dist:chrome": "npm run build:prod && gulp dist:chrome", - "dist:chrome:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist:chrome", - "dist:firefox": "npm run build:prod && gulp dist:firefox", - "dist:opera": "npm run build:prod && gulp dist:opera", - "dist:safari": "cross-env BROWSER=safari npm run build:prod && gulp dist:safari", - "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 BROWSER=safari run build:prod && cross-env MANIFEST_VERSION=3 BROWSER=safari gulp dist:safari", - "dist:safari:mas": "npm run build:prod && gulp dist:safari:mas", - "dist:safari:masdev": "npm run build:prod && gulp dist:safari:masdev", - "dist:safari:dmg": "npm run build:prod && gulp dist:safari:dmg", + "build:watch:chrome": "npm run build:chrome -- --watch", + "build:watch:edge": "npm run build:edge -- --watch", + "build:watch:firefox": "npm run build:firefox -- --watch", + "build:watch:opera": "npm run build:opera -- --watch", + "build:watch:safari": "npm run build:safari -- --watch", + "build:prod:chrome": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:chrome", + "build:prod:edge": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:edge", + "build:prod:firefox": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:firefox", + "build:prod:opera": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:opera", + "build:prod:safari": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:safari", + "dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.ps1 dist-chrome.zip", + "dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.ps1 dist-edge.zip", + "dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.ps1 dist-firefox.zip", + "dist:opera": "npm run build:prod:opera && mkdir -p dist && ./scripts/compress.ps1 dist-opera.zip", + "dist:safari": "npm run build:prod:safari && ./scripts/package-safari.ps1", + "dist:edge:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:edge", + "dist:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:firefox", + "dist:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:opera", + "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", "test": "jest", "test:watch": "jest --watch", - "test:watch:all": "jest --watchAll" + "test:watch:all": "jest --watchAll", + "test:clearCache": "jest --clear-cache" } } diff --git a/apps/browser/postcss.config.js b/apps/browser/postcss.config.js index c4513687e89..83e237f06e5 100644 --- a/apps/browser/postcss.config.js +++ b/apps/browser/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/browser/scripts/compress.ps1 b/apps/browser/scripts/compress.ps1 new file mode 100755 index 00000000000..981a07c3c8c --- /dev/null +++ b/apps/browser/scripts/compress.ps1 @@ -0,0 +1,30 @@ +#!/usr/bin/env pwsh + +#### +# Compress the build directory into a zip file. +#### + +param ( + [Parameter(Mandatory = $true)] + [String] $fileName +) + +$buildDir = Join-Path $PSScriptRoot "../build" +$distDir = Join-Path $PSScriptRoot "../dist" + +# Create dist directory if it doesn't exist +if (-not (Test-Path $distDir)) { + New-Item -ItemType Directory -Path $distDir +} + +$distPath = Join-Path -Path $distDir -ChildPath $fileName + +if (Test-Path $distPath) { + Remove-Item $distPath +} + +# Compress build directory +if (Test-Path $buildDir) { + Compress-Archive -Path (Join-Path $buildDir "*") -DestinationPath $distPath + Write-Output "Zipped $buildDir into $distPath" +} diff --git a/apps/browser/scripts/package-safari.ps1 b/apps/browser/scripts/package-safari.ps1 new file mode 100755 index 00000000000..075ed606070 --- /dev/null +++ b/apps/browser/scripts/package-safari.ps1 @@ -0,0 +1,112 @@ +#!/usr/bin/env pwsh + +#### +# Builds the safari appex. +#### + +$buildDir = Join-Path $PSScriptRoot "../build" +$distDir = Join-Path $PSScriptRoot "../dist" + +Write-Output $PSScriptRoot + +if (-not (Test-Path $buildDir)) { + Write-Output "No build directory found. Exiting..." + exit +} + +# Create dist directory if it doesn't exist +if (-not (Test-Path $distDir)) { + New-Item -ItemType Directory -Path $distDir +} + +$subBuildPaths = @("mas", "masdev", "dmg") +$safariSrc = Join-Path $PSScriptRoot "../src/safari" +$safariDistPath = Join-Path -Path $distDir -ChildPath "Safari" + +if (-not (Test-Path $safariDistPath)) { + New-Item -ItemType Directory -Path $safariDistPath +} + +# Delete old safari dists +Remove-Item -LiteralPath $safariDistPath -Force -Recurse + +foreach ($subBuildPath in $subBuildPaths) { + $safariBuildPath = Join-Path -Path $safariDistPath -ChildPath $subBuildPath + $builtAppexPath = Join-Path -Path $safariBuildPath -ChildPath "build/Release/safari.appex" + $builtAppexFrameworkPath = Join-Path -Path $safariBuildPath -ChildPath "build/Release/safari.appex/Contents/Frameworks/" + $entitlementsPath = Join-Path -Path $safariSrc -ChildPath "safari/safari.entitlements" + + switch ($subBuildPath) { + "mas" { + $codesignArgs = @( + "--verbose", + "--force", + "--sign", + '"3rd Party Mac Developer Application: Bitwarden Inc"', + "--entitlements", + $entitlementsPath + ) + } + "masdev" { + $codesignArgs = @( + "--verbose", + "--force", + "--sign", + "E7C9978F6FBCE0553429185C405E61F5380BE8EB", + "--entitlements", + $entitlementsPath + ) + } + "dmg" { + $codesignArgs = @( + "--verbose", + "--force", + "-o", + "runtime", + "--sign", + '"Developer ID Application: 8bit Solutions LLC"', + "--entitlements", + $entitlementsPath + ) + } + } + + # Copy safari src + Copy-Item -Path $safariSrc -Destination $safariBuildPath -Recurse + + # Copy build + $target = Join-Path -Path $safariBuildPath -ChildPath "safari/app" + Copy-Item -Path $buildDir -Destination $target -Recurse + + # Update versions + $jsonFilePath = Join-Path $buildDir "manifest.json" + $jsonContent = Get-Content -Path $jsonFilePath -Raw + $jsonObject = $jsonContent | ConvertFrom-Json + + $infoFile = Join-Path -Path $safariBuildPath -ChildPath "safari/Info.plist" + (Get-Content $infoFile).Replace('0.0.1', $jsonObject.version).Replace('0.0.2', $jsonObject.version) | Set-Content $infoFile + + $projectFile = Join-Path -Path $safariBuildPath -ChildPath "desktop.xcodeproj/project.pbxproj" + (Get-Content $projectFile).Replace('../../../build', "../safari/app") | Set-Content $projectFile + + # Build using xcode + $xcodeBuildArgs = @( + "-project", + (Join-Path $safariBuildPath "desktop.xcodeproj"), + "-alltargets", + "-configuration", + "Release" + ) + $proc = Start-Process "xcodebuild" -ArgumentList $xcodeBuildArgs -NoNewWindow -PassThru + $proc.WaitForExit() + + # Codesign + $libs = Get-ChildItem -Path $builtAppexFrameworkPath -Filter "*.dylib" + foreach ($lib in $libs) { + $proc = Start-Process "codesign" -ArgumentList ($codesignArgs + $lib.FullName) -NoNewWindow -PassThru + $proc.WaitForExit() + } + + $proc = Start-Process "codesign" -ArgumentList ($codesignArgs + $builtAppexPath) -NoNewWindow -PassThru + $proc.WaitForExit() +} diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index b2f849964fa..8b4bbe23e04 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -3,39 +3,39 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "مدير كلمات المرور Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "في المنزل، في العمل، أو في أثناء التنقل، يقوم Bitwarden بتأمين جميع كلمات المرور والمعلومات الحساسة بسهولة", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "قم بالتسجيل أو إنشاء حساب جديد للوصول إلى خزنتك الآمنة." + "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": "Set a strong password" + "message": "أنشئ كلمة مرور قوية" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "أكمل إنشاء حسابك عن طريق تعيين كلمة مرور" }, "enterpriseSingleSignOn": { "message": "تسجيل الدخول الأُحادي للمؤسسات – SSO" @@ -56,13 +56,13 @@ "message": "كلمة المرور الرئيسية" }, "masterPassDesc": { - "message": "كلمة المرور الرئيسية هي كلمة المرور التي تستخدمها للوصول إلى خزنتك. من المهم جدا ألا تنسى كلمة المرور الرئيسية. لا توجد طريقة لاسترداد كلمة المرور في حال نسيتها." + "message": "كلمة المرور الرئيسية هي كلمة المرور التي تستخدمها للوصول إلى خزانتك. من المهم جدا ألا تنسى كلمة المرور الرئيسية. لا توجد طريقة لاسترداد كلمة المرور في حال نسيتها." }, "masterPassHintDesc": { "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,10 +81,10 @@ "message": "تلميح كلمة المرور الرئيسية (إختياري)" }, "joinOrganization": { - "message": "Join organization" + "message": "انضم إلى المنظمة" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "انضم إلى $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -93,19 +93,19 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "أنهي الانضمام إلى هذه المؤسسة عن طريق تعيين كلمة مرور رئيسية." }, "tab": { "message": "علامة تبويب" }, "vault": { - "message": "الخزنة" + "message": "الخزانة" }, "myVault": { - "message": "خزنتي" + "message": "خزانتي" }, "allVaults": { - "message": "جميع الخزنات" + "message": "جميع الخزانات" }, "tools": { "message": "الأدوات" @@ -120,7 +120,7 @@ "message": "نسخ كلمة المرور" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "انسخ عبارة المرور" }, "copyNote": { "message": "نسخ الملاحظة" @@ -138,22 +138,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": "نسخ المفتاح الخاص" + }, + "copyPublicKey": { + "message": "نسخ المفتاح العام" + }, + "copyFingerprint": { + "message": "نسخ البصمة" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "نسخ $FIELD$", "placeholders": { "field": { "content": "$1", @@ -162,13 +171,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "نسخ الموقع الإلكتروني" }, "copyNotes": { - "message": "Copy notes" + "message": "نسخ الملاحظات" }, "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "هوية التعبئة التلقائية" }, + "fillVerificationCode": { + "message": "ملء رمز التحقق" + }, + "fillVerificationCodeAria": { + "message": "ملء رمز التحقق", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "إنشاء كلمة مرور (تم النسخ)" }, @@ -208,10 +224,10 @@ "message": "إضافة هوية" }, "unlockVaultMenu": { - "message": "افتح خزنتك" + "message": "افتح خزانتك" }, "loginToVaultMenu": { - "message": "تسجيل الدخول إلى خزنتك" + "message": "تسجيل الدخول إلى خزانتك" }, "autoFillInfo": { "message": "لا توجد تسجيلات دخول متاحة للملء التلقائي في علامة تبويب المتصفح الحالية." @@ -223,16 +239,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": "أدخل عنوان البريد الإلكتروني لحسابك وسيُرسل تلميح كلمة المرور الخاصة بك إليك" }, "passwordHint": { "message": "تلميح كلمة المرور" @@ -265,25 +281,25 @@ "message": "تغيير كلمة المرور الرئيسية" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "هل تريد المتابعة إلى تطبيق الويب؟" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "استكشف المزيد من الميزات لحساب Bitwarden الخاص بك على تطبيق الويب." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "هل تريد المتابعة إلى مركز المساعدة؟" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "تعرف على المزيد حول كيفية استخدام Bitwarden في مركز المساعدة." }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "هل تريد المتابعة إلى متجر إضافات المتصفح؟" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "ساعد الآخرين في معرفة ما إذا كان Bitwarden مناسب لهم. قم بزيارة متجر إضافات المتصفح الخاص بك واترك تقييمًا الآن." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "يمكنك تغيير كلمة المرور الرئيسية الخاصة بك على تطبيق ويب الخاص ب Bitwarden." }, "fingerprintPhrase": { "message": "عبارة بصمة الإصبع", @@ -300,43 +316,43 @@ "message": "تسجيل الخروج" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "حول Bitwarden" }, "about": { "message": "عن التطبيق" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "المزيد من Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "هل تريد المتابعة إلى bitwarden.com؟" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden للأعمال التجارية" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "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 يسمح لك بتخزين مفاتيح المصادقة وإنشاء رموز لمرة واحدة المستندة إلى الوقت لعمليات المصادقة الثنائية. تعرف على المزيد على موقع 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": "Free Bitwarden Families" + "message": "Bitwarden للعائلات المجاني" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "أنت مؤهل للحصول على Bitwarden للعائلات المجاني. أحصل على هذا العرض اليوم عبر تطبيق الويب." }, "version": { "message": "الإصدار" @@ -357,22 +373,22 @@ "message": "تحرير المجلّد" }, "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" }, "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": "حذف المجلّد" @@ -399,7 +415,7 @@ "message": "المزامنة" }, "syncVaultNow": { - "message": "مزامنة الخزنة الآن" + "message": "مزامنة الخزانة الآن" }, "lastSync": { "message": "آخر مزامنة:" @@ -415,7 +431,7 @@ "message": "قم بإنشاء كلمات مرور قوية وفريدة لتسجيلات الدخول الخاصة بك." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "تطبيق ويب Bitwarden" }, "importItems": { "message": "استيراد العناصر" @@ -427,7 +443,7 @@ "message": "توليد كلمة مرور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "توليد عبارة المرور" }, "regeneratePassword": { "message": "إعادة توليد كلمة المرور" @@ -438,9 +454,6 @@ "length": { "message": "الطول" }, - "passwordMinLength": { - "message": "الحد الأدنى لطول كلمة السر" - }, "uppercase": { "message": "أحرف كبيرة (من A إلى Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -458,11 +471,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "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": { @@ -470,7 +483,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": { @@ -478,7 +491,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": { @@ -486,7 +499,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" }, "specialCharactersLabel": { @@ -512,20 +525,16 @@ "minSpecial": { "message": "الحد الأدنى من الأحرف الخاصة" }, - "avoidAmbChar": { - "message": "تجنب الأحرف الغامضة", - "description": "deprecated. Use avoidAmbiguous instead." - }, "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": { - "message": "البحث في الخزنة" + "message": "البحث في الخزانة" }, "edit": { "message": "تعديل" @@ -555,19 +564,19 @@ "message": "المفضلات" }, "unfavorite": { - "message": "Unfavorite" + "message": "إزالة من المفضلة" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "تم إضافة العنصر إلى المفضلات" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "تم إزالة العنصر من المفضلات" }, "notes": { "message": "الملاحظات" }, "privateNote": { - "message": "Private note" + "message": "ملاحظة سرية" }, "note": { "message": "الملاحظة" @@ -588,10 +597,10 @@ "message": "بدء" }, "launchWebsite": { - "message": "Launch website" + "message": "تشغيل الموقع" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "تشغيل الموقع $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -612,7 +621,7 @@ "message": "الأخرى" }, "unlockMethods": { - "message": "Unlock options" + "message": "فتح الخيارات" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "أعدنّ طريقة إلغاء القُفْل لتغيير إجراء مهلة المخزن الخاص بك." @@ -621,20 +630,17 @@ "message": "إعداد طريقة إلغاء القفل في الإعدادات" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "مهلة الجَلسة" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "مهلة الخزنة" }, "otherOptions": { - "message": "Other options" + "message": "خيارات أخرى" }, "rateExtension": { "message": "قيِّم هذه الإضافة" }, - "rateExtensionDesc": { - "message": "يرجى النظر في مساعدتنا بكتابة تعليق إيجابي!" - }, "browserNotSupportClipboard": { "message": "متصفح الويب الخاص بك لا يدعم خاصية النسخ السهل. يرجى استخدام النسخ اليدوي." }, @@ -642,16 +648,16 @@ "message": "قم بتأكيد هويتك" }, "yourVaultIsLocked": { - "message": "خزنتك مقفلة. قم بتأكيد هويتك للمتابعة." + "message": "خزانتك مقفلة. قم بتأكيد هويتك للمتابعة." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "المخزن الخاص بك مقفل" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حسابك مقفل" }, "or": { - "message": "or" + "message": "أو" }, "unlock": { "message": "إلغاء القفل" @@ -673,10 +679,10 @@ "message": "كلمة المرور الرئيسية غير صالحة" }, "vaultTimeout": { - "message": "نفذ وقت الخزنة" + "message": "نفذ وقت الخزانة" }, "vaultTimeout1": { - "message": "Timeout" + "message": "المهلة" }, "lockNow": { "message": "إقفل الآن" @@ -730,16 +736,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": "لقد حدث خطأ ما" @@ -773,10 +779,10 @@ "message": "تم إنشاء حسابك الجديد! يمكنك الآن تسجيل الدخول." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "تم إنشاء حسابك الجديد!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "لقد قمت بتسجيل الدخول!" }, "youSuccessfullyLoggedIn": { "message": "سجلتَ الدخول بنجاح" @@ -791,7 +797,7 @@ "message": "رمز التحقق مطلوب." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "تم إلغاء المصادقة أو استغرقت وقتا طويلا. الرجاء المحاولة مرة أخرى." }, "invalidVerificationCode": { "message": "رمز التحقق غير صالح" @@ -819,16 +825,16 @@ "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": "نسخ مفتاح المصادقة (TOTP)" @@ -837,28 +843,28 @@ "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" }, "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": "هل أنت متأكد من أنك تريد تسجيل الخروج؟" @@ -879,13 +885,13 @@ "message": "أُضيف المجلد" }, "twoStepLoginConfirmation": { - "message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزنة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟" + "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": "حُفظ المجلد" @@ -928,7 +934,7 @@ "message": "رابط جديد" }, "addDomain": { - "message": "Add domain", + "message": "أضف نطاق", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -975,7 +981,7 @@ "message": "Save to vault options" }, "addLoginNotificationDesc": { - "message": "اطلب إضافة عنصر إذا لم يُعثر عليه في خزنتك." + "message": "اطلب إضافة عنصر إذا لم يُعثر عليه في خزانتك." }, "addLoginNotificationDescAlt": { "message": "اطلب إضافة عنصر إذا لم يتم العثور على عنصر في المخزن الخاص بك. ينطبق على جميع حسابات تسجيل الدخول." @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "قائمة عناصر الهوية في صفحة التبويب لسهولة الملء التلقائي." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "مسح الحافظة", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1040,7 +1049,7 @@ "message": "إلغاء القفل" }, "additionalOptions": { - "message": "Additional options" + "message": "خيارات إضافية" }, "enableContextMenuItem": { "message": "إظهار خيارات قائمة السياق" @@ -1080,22 +1089,22 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "التصدير من" }, "exportVault": { - "message": "تصدير الخزنة" + "message": "تصدير الخزانة" }, "fileFormat": { "message": "صيغة الملف" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "سيكون المِلَفّ المُصدر محميًا بكلمة مرور وسيتطلب كلمة مرور المِلَفّ لفك تشفيره." }, "filePassword": { - "message": "File password" + "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." @@ -1116,11 +1125,15 @@ "message": "تحذير", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { - "message": "تأكيد تصدير الخزنة" + "message": "تأكيد تصدير الخزانة" }, "exportWarningDesc": { - "message": "يحتوي هذا التصدير على بيانات خزنتك بتنسيق غير مشفر. لا يجب عليك تخزين أو إرسال الملف الذي تم تصديره عبر قنوات غير آمنة (مثل البريد الإلكتروني). احذفه مباشرة بعد انتهائك من استخدامه." + "message": "يحتوي هذا التصدير على بيانات خزانتك بتنسيق غير مشفر. لا يجب عليك تخزين أو إرسال الملف الذي تم تصديره عبر قنوات غير آمنة (مثل البريد الإلكتروني). احذفه مباشرة بعد انتهائك من استخدامه." }, "encExportKeyWarningDesc": { "message": "يقوم هذا التصدير بتشفير بياناتك باستخدام مفتاح تشفير حسابك. إذا قمت بتدوير مفتاح تشفير حسابك يجب عليك التصدير مرة أخرى لأنك لن تتمكن من فك تشفير ملف التصدير هذا." @@ -1129,7 +1142,7 @@ "message": "مفاتيح تشفير الحساب فريدة من نوعها لكل حساب مستخدم Bitwarden، لذلك لا يمكنك استيراد تصدير مشفر إلى حساب آخر." }, "exportMasterPassword": { - "message": "أدخل كلمة المرور الرئيسية لتصدير بيانات خزنتك." + "message": "أدخل كلمة المرور الرئيسية لتصدير بيانات خزانتك." }, "shared": { "message": "مشترك" @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "الانتقال إلى مؤسسة" }, - "share": { - "message": "مشاركة" - }, "movedItemToOrg": { "message": "$ITEMNAME$ انتقل إلى $ORGNAME$", "placeholders": { @@ -1238,10 +1248,10 @@ "message": "خيارات تسجيل الدخول بخطوتين المملوكة لجهات اخرى مثل YubiKey و Duo." }, "ppremiumSignUpReports": { - "message": "نظافة كلمة المرور، صحة الحساب، وتقارير تسريبات البيانات للحفاظ على سلامة خزنتك." + "message": "نظافة كلمة المرور، صحة الحساب، وتقارير تسريبات البيانات للحفاظ على سلامة خزانتك." }, "ppremiumSignUpTotp": { - "message": "مورد رمز التحقق (2FA) لتسجيل الدخول في خزنتك." + "message": "مورد رمز التحقق (2FA) لتسجيل الدخول في خزانتك." }, "ppremiumSignUpSupport": { "message": "أولوية دعم العملاء." @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "أدخل رمز التحقق من 6 أرقام من تطبيق المصادقة الخاص بك." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "أدخل رمز التحقق المكون من 6 أرقام الذي تم إرساله إلى $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "إذا تم اكتشاف نموذج تسجيل الدخول، يتم التعبئة التلقائية عند تحميل صفحة الويب." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "مواقع المساومة أو غير الموثوق بها يمكن أن تستغل الملء التلقائي في تحميل الصفحة." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "الهوية" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "ملاحظات آمنة" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "مسح", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "Clear history" }, "nothingToShow": { - "message": "Nothing to show" + "message": "لا يوجد شيء لعرضه" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "لم تقم بتوليد أي شيء مؤخرًا" }, "remove": { "message": "إزالة" @@ -1984,16 +1993,16 @@ "message": "فتح باستخدام رمز PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "تعيين رَقَم التعريف الشخصي" }, "setYourPinButton": { - "message": "Set PIN" + "message": "تعيين رَقَم التعريف الشخصي" }, "setYourPinCode": { "message": "تعيين رمز PIN الخاص بك لإلغاء قفل Bitwarden. سيتم إعادة تعيين إعدادات PIN الخاصة بك إذا قمت بتسجيل الخروج بالكامل من التطبيق." }, "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": "رمز PIN مطلوب." @@ -2008,7 +2017,7 @@ "message": "فتح باستخدام القياسات الحيوية" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "فتح بكلمة المرور الرئيسية" }, "awaitDesktop": { "message": "في انتظار التأكيد من سطح المكتب" @@ -2020,7 +2029,7 @@ "message": "قفل مع كلمة المرور الرئيسية عند إعادة تشغيل المتصفح" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "أطلب كلمة المرور الرئيسية عند إعادة تشغيل المتصفح" }, "selectOneCollection": { "message": "يجب عليك تحديد مجموعة واحدة على الأقل." @@ -2031,30 +2040,27 @@ "clone": { "message": "استنساخ" }, - "passwordGeneratorPolicyInEffect": { - "message": "واحدة أو أكثر من سياسات المؤسسة تؤثر على إعدادات المولدات الخاصة بك." - }, "passwordGenerator": { - "message": "Password generator" + "message": "مولد كلمة المرور" }, "usernameGenerator": { - "message": "Username generator" + "message": "مولد اسم المستخدم" }, "useThisPassword": { - "message": "Use this password" + "message": "استخدم كلمة المرور هذه" }, "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'" }, "vaultTimeoutAction": { @@ -2090,10 +2096,10 @@ "message": "تم استعادة العنصر" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "لديك حساب بالفعل؟" }, "vaultTimeoutLogOutConfirmation": { - "message": "سيؤدي تسجيل الخروج إلى إزالة جميع إمكانية الوصول إلى خزنتك ويتطلب المصادقة عبر الإنترنت بعد انتهاء المهلة. هل أنت متأكد من أنك تريد استخدام هذا الإعداد؟" + "message": "سيؤدي تسجيل الخروج إلى إزالة جميع إمكانية الوصول إلى خزانتك ويتطلب المصادقة عبر الإنترنت بعد انتهاء المهلة. هل أنت متأكد من أنك تريد استخدام هذا الإعداد؟" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "تأكيد إجراء المهلة" @@ -2102,7 +2108,7 @@ "message": "التعبئة التلقائية والحفظ" }, "fillAndSave": { - "message": "Fill and save" + "message": "عبء ثم احفظ" }, "autoFillSuccessAndSavedUri": { "message": "تم تعبئة العنصر تلقائياً وحفظ عنوان URI" @@ -2183,19 +2189,19 @@ "message": "كلمة المرور الرئيسية الجديدة لا تفي بمتطلبات السياسة العامة." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "احصل على النصائح والإعلانات وفرص البحوث من Bitwarden في صندوق الوارد الخاص بك." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "إلغاء الاشتراك" }, "atAnyTime": { - "message": "at any time." + "message": "في أي وقت." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "عن طريق المتابعة، أنت توافق على" }, "and": { - "message": "and" + "message": "و" }, "acceptPolicies": { "message": "من خلال تحديد هذا المربع فإنك توافق على ما يلي:" @@ -2258,7 +2264,7 @@ "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." @@ -2315,9 +2321,12 @@ "message": "An organization policy has blocked importing items into your individual vault." }, "domainsTitle": { - "message": "Domains", + "message": "النطاقات", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "النطاقات المستبعدة" }, @@ -2325,10 +2334,19 @@ "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." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "الموقع $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "تم حفظ تغييرات استبعاد النطاقات" }, "limitSendViews": { "message": "Limit views" }, "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": { @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "نص" }, @@ -2391,22 +2404,15 @@ "message": "ملف" }, "allSends": { - "message": "All Sends", + "message": "كل الإرسالات", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" - }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + "message": "إخفاء النص بشكل افتراضي" }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "تاريخ الحذف" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "تاريخ انتهاء الصلاحية" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "يوم واحد" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "مُخصّص" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "قبل أن تبدأ" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "لاستخدام منتقي التاريخ على نمط التقويم", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "انقر هنا", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "أن يخرج من النافذة.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "صلاحية تاريخ الانتهاء المقدّم غير صحيح." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2671,7 +2607,7 @@ "message": "Email verified" }, "emailVerificationRequiredDesc": { - "message": "يجب عليك تأكيد بريدك الإلكتروني لاستخدام هذه الميزة. يمكنك تأكيد بريدك الإلكتروني في خزنة الويب." + "message": "يجب عليك تأكيد بريدك الإلكتروني لاستخدام هذه الميزة. يمكنك تأكيد بريدك الإلكتروني في خزانة الويب." }, "updatedMasterPassword": { "message": "Updated master password" @@ -2680,7 +2616,7 @@ "message": "تحديث كلمة المرور الرئيسية" }, "updateMasterPasswordWarning": { - "message": "تم تغيير كلمة المرور الرئيسية الخاصة بك مؤخرًا من قبل مسؤول في مؤسستك. من أجل الوصول إلى الخزنة، يجب عليك تحديثها الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. قد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." + "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." @@ -2732,7 +2668,7 @@ "message": "Enterprise policy requirements have been applied to your timeout options" }, "vaultTimeoutPolicyInEffect": { - "message": "سياسات مؤسستك تؤثر على مهلة الخزنة الخاص بك. الحد الأقصى المسموح به لمهلة الخزنة هو $HOURS$ ساعة/ساعات و $MINUTES$ دقيقة/دقائق", + "message": "سياسات مؤسستك تؤثر على مهلة الخزانة الخاص بك. الحد الأقصى المسموح به لمهلة الخزانة هو $HOURS$ ساعة/ساعات و $MINUTES$ دقيقة/دقائق.", "placeholders": { "hours": { "content": "$1", @@ -2797,10 +2733,10 @@ } }, "vaultTimeoutTooLarge": { - "message": "مهلة خزنتك تتجاوز القيود التي تضعها مؤسستك." + "message": "مهلة خزانتك تتجاوز القيود التي تضعها مؤسستك." }, "vaultExportDisabled": { - "message": "تصدير الخزنة مُعطّل" + "message": "تصدير الخزانة مُعطّل" }, "personalVaultExportPolicyInEffect": { "message": "واحدة أو أكثر من سياسات المؤسسة تمنعك من تصدير خزانتك الشخصية." @@ -2842,7 +2778,7 @@ "message": "انتهت مدة جلستك. يرجى العودة ومحاولة تسجيل الدخول مرة أخرى." }, "exportingPersonalVaultTitle": { - "message": "جاري تصدير الخزنة الشخصية" + "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.", @@ -2868,8 +2804,19 @@ "error": { "message": "خطأ" }, - "regenerateUsername": { - "message": "إعادة إنشاء اسم المستخدم" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "إنشاء اسم المستخدم" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "نوع اسم المستخدم" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "اسم الموقع الإلكتروني" }, - "whatWouldYouLikeToGenerate": { - "message": "ما الذي ترغب في توليده؟" - }, - "passwordType": { - "message": "نوع كلمة المرور" - }, "service": { "message": "الخدمة" }, @@ -3133,17 +3091,32 @@ "message": "عبارة بصمة الإصبع" }, "fingerprintMatchInfo": { - "message": "الرجاء التأكد من أن الخزنة الخاصة بك غير مقفلة وأن عبارة بصمة الإصبع تتطابق على الجهاز الآخر." + "message": "الرجاء التأكد من أن الخزانة الخاصة بك غير مقفلة وأن عبارة بصمة الإصبع تتطابق على الجهاز الآخر." }, "resendNotification": { "message": "إعادة إرسال الإشعار" }, - "viewAllLoginOptions": { - "message": "عرض جميع خيارات تسجيل الدخول" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "تم إرسال إشعار إلى جهازك." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "بَدْء تسجيل الدخول" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "تُفتح في نافذة جديدة" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "موافقة الجهاز مطلوبة. حدّد خيار الموافقة أدناه:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "تذكر هذا الجهاز" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "ملء بيانات الاعتماد لـ", "description": "Screen reader text for when overlay item is in focused" @@ -3732,7 +3725,7 @@ } }, "confirmVaultImport": { - "message": "تأكيد تصدير الخزنة" + "message": "تأكيد تصدير الخزانة" }, "confirmVaultImportDesc": { "message": "هذا الملف محمي بكلمة مرور. الرجاء إدخال كلمة مرور الملف لاستيراد البيانات." @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "لن يتم نسخ Passkey" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 94b1d9ea4c7..86f1d07fb3f 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -26,7 +26,7 @@ "message": "Keçid açarı ilə giriş et" }, "useSingleSignOn": { - "message": "Tək daxil olma üsulunu istifadə et" + "message": "Vahid daxil olma üsulunu istifadə et" }, "welcomeBack": { "message": "Yenidən xoş gəlmisiniz" @@ -38,7 +38,7 @@ "message": "Bir parol təyin edərək hesabınızı yaratmağı başa çatdırın" }, "enterpriseSingleSignOn": { - "message": "Müəssisə üçün tək daxil olma" + "message": "Müəssisə üçün vahid daxil olma" }, "cancel": { "message": "İmtina" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Lisenziya nömrəsini kopyala" }, + "copyPrivateKey": { + "message": "Private açarı kopyala" + }, + "copyPublicKey": { + "message": "Public açarı kopyala" + }, + "copyFingerprint": { + "message": "Barmaq izini kopyala" + }, "copyCustomField": { "message": "$FIELD$ - kopyala", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Kimliyi avto-doldur" }, + "fillVerificationCode": { + "message": "Doğrulama kodunu doldur" + }, + "fillVerificationCodeAria": { + "message": "Doğrulama Kodunu Doldur", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Parol yarat (kopyalandı)" }, @@ -438,9 +454,6 @@ "length": { "message": "Uzunluq" }, - "passwordMinLength": { - "message": "Minimal parol uzunluğu" - }, "uppercase": { "message": "Böyük hərf (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum simvol" }, - "avoidAmbChar": { - "message": "Anlaşılmaz simvollardan çəkinin", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Anlaşılmaz xarakterlərdən çəkin", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Uzantını qiymətləndir" }, - "rateExtensionDesc": { - "message": "Gözəl bir rəy ilə bizə dəstək ola bilərsiniz!" - }, "browserNotSupportClipboard": { "message": "Veb brauzeriniz lövhəyə kopyalamağı dəstəkləmir. Əvəzində əllə kopyalayın." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Asan avto-doldurma üçün Vərəq səhifəsində kimlik elementlərini sadalayın." }, + "clickToAutofillOnVault": { + "message": "Seyf görünüşündə avto-doldurmaq üçün elementlərə klikləyin" + }, "clearClipboard": { "message": "Lövhəni təmizlə", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "XƏBƏRDARLIQ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Xəbərdarlıq", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Seyfi xaricə köçürməyi təsdiqlə" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Təşkilata daşı" }, - "share": { - "message": "Paylaş" - }, "movedItemToOrg": { "message": "$ITEMNAME$, $ORGNAME$ şirkətinə daşındı", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Kimlik doğrulayıcı tətbiqindən 6 rəqəmli doğrulama kodunu daxil edin." }, + "authenticationTimeout": { + "message": "Kimlik doğrulama vaxtı bitdi" + }, + "authenticationSessionTimedOut": { + "message": "Kimlik doğrulama seansının vaxtı bitdi. Lütfən giriş prosesini yenidən başladın." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ ünvanına göndərilən e-poçtdakı 6 rəqəmli doğrulama kodunu daxil edin.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Giriş formu aşkarlananda, səhifə yüklənən zaman formu avto-doldurma icra edilsin." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Xəbərdarlıq:$CLOSETAG$ Təhlükəsizliyi pozulmuş və ya güvənilməyən veb saytlar, səhifə yüklənəndə avto-doldurmanı istifadə edə bilər.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Təhlükəli və ya güvənilməyən veb saytlar, səhifə yüklənərkən avto-doldurmanı istifadə edə bilər." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Kimlik" }, + "typeSshKey": { + "message": "SSH açarı" + }, "newItemHeader": { "message": "Yeni $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Güvənli qeydlər" }, + "sshKeys": { + "message": "SSH Açarları" + }, "clear": { "message": "Təmizlə", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Klonla" }, - "passwordGeneratorPolicyInEffect": { - "message": "Bir və ya daha çox təşkilat siyasəti yaradıcı ayarlarınıza təsir edir." - }, "passwordGenerator": { "message": "Parol yaradıcı" }, @@ -2318,6 +2324,9 @@ "message": "Domenlər", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Əngəllənmiş domenlər" + }, "excludedDomains": { "message": "İstisna edilən domenlər" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, giriş etmiş bütün hesablar üçün bu domenlərin giriş detallarını saxlamağı soruşmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." }, + "blockedDomainsDesc": { + "message": "Bu veb saytlar üçün avto-doldurma və digər əlaqəli özəlliklər təklif olunmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." + }, + "autofillBlockedNotice": { + "message": "Bu veb sayt üçün avto-doldurma əngəllənib. Bunu ayarlarda incələyin və ya dəyişdirin." + }, + "autofillBlockedTooltip": { + "message": "Bu veb saytda avto-doldurma əngəllənib. Ayarlarda incələyin." + }, "websiteItemLabel": { "message": "Veb sayt $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Əngəllənmiş domen dəyişiklikləri saxlanıldı" + }, "excludedDomainsSavedSuccess": { "message": "İstisna domen dəyişikliyi saxlanıldı" }, @@ -2373,14 +2394,6 @@ "message": "Send detalları", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "\"Send\"ləri axtar", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "\"Send\" əlavə et", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Mətn" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Mətni ilkin olaraq gizlət" }, - "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." - }, "expired": { "message": "Müddəti bitib" }, - "pendingDeletion": { - "message": "Silinməsi gözlənilir" - }, "passwordProtected": { "message": "Parolla qorunan" }, @@ -2456,24 +2462,9 @@ "message": "\"Send\"ə düzəliş et", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "\"Send\"in növü nədir?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Bu \"Send\"i açıqlayan bir ad.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Göndərmək istədiyiniz fayl." - }, "deletionDate": { "message": "Silinmə tarixi" }, - "deletionDateDesc": { - "message": "\"Send\" göstərilən tarix və saatda birdəfəlik silinəcək.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send, bu tarixdə həmişəlik silinəcək.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Bitmə tarixi" }, - "expirationDateDesc": { - "message": "Əgər ayarlanıbsa, göstərilən tarix və vaxtda \"Send\"ə müraciət başa çatacaq.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 gün" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Özəl" }, - "maximumAccessCount": { - "message": "Maksimal müraciət sayı" - }, - "maximumAccessCountDesc": { - "message": "Əgər ayarlanıbsa, istifadəçilər maksimal müraciət sayına çatdıqdan sonra bu \"Send\"ə müraciət edə bilməyəcək.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "İstəyinizə görə istifadəçilərdən bu \"Send\"ə müraciət edərkən parol tələb edə bilərsiniz.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Alıcıların bu \"Send\"ə müraciət etməsi üçün ixtiyari bir parol əlavə edin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Bu \"Send\" ilə bağlı gizli notlar.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Heç kimin müraciət edə bilməməsi üçün bu \"Send\"i deaktiv et.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Saxladıqdan sonra \"Send\"in keçidini lövhəyə kopyala.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Göndərmək istədiyiniz mətn" - }, - "sendHideText": { - "message": "Bu \"Send\"in mətnini ilkin olaraq gizlət", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Hazırkı müraciət sayı" - }, "createSend": { "message": "Yeni \"Send\"", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Başlamazdan əvvəl" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Təqvim stilində tarix seçici istifadə etmək üçün", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "bura klikləyin", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "yeni bir pəncərə açın.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Göstərilən son istifadə tarixi yararsızdır." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Silinmə və son istifadə tarixlərini saxlayarkən xəta baş verdi." }, - "hideEmail": { - "message": "E-poçt ünvanımı alıcılardan gizlət." - }, "hideYourEmail": { "message": "E-poçt ünvanınız baxanlardan gizlədilsin." }, - "sendOptionsPolicyInEffect": { - "message": "Bir və ya daha çox təşkilat siyasətləri \"Send\" seçimlərinizə təsir edir." - }, "passwordPrompt": { "message": "Ana parolu təkrar soruş" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Xəta" }, - "regenerateUsername": { - "message": "İstifadəçi adını yenidən yarat" + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "İstifadəçi adı yarat" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "E-poçt yarat" }, - "generatorBoundariesHint": { - "message": "Dəyər $MIN$-$MAX$ arasında olmalıdır", + "spinboxBoundariesHint": { + "message": "Dəyər, $MIN$-$MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "İstifadəçi adı növü" + "passwordLengthRecommendationHint": { + "message": " Güclü bir parol yaratmaq üçün $RECOMMENDED$ və ya daha çox xarakter istifadə edin.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Güclü bir keçid ifadəsi yaratmaq üçün $RECOMMENDED$ və ya daha çox söz istifadə edin.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Üstəgəl ünvanlı e-poçt", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Veb sayt adı" }, - "whatWouldYouLikeToGenerate": { - "message": "Nə yaratmaq istəyirsiniz?" - }, - "passwordType": { - "message": "Parol növü" - }, "service": { "message": "Xidmət" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Bildirişi təkrar göndər" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Bütün giriş seçimlərinə bax" + }, + "viewAllLoginOptionsV1": { "message": "Bütün giriş seçimlərinə bax" }, "notificationSentDevice": { "message": "Cihazınıza bir bildiriş göndərildi." }, + "aNotificationWasSentToYourDevice": { + "message": "Cihazınıza bir bildiriş göndərildi" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Hesabınızın kilidinin açıq olduğuna və barmaq izi ifadəsinin digər cihazda uyuşduğuna əmin olun" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Tələbiniz təsdiqləndikdə bildiriş alacaqsınız" + }, + "needAnotherOptionV1": { + "message": "Başqa bir seçimə ehtiyacınız var?" + }, "loginInitiated": { "message": "Giriş başladıldı" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Yeni bir pəncərədə açılır" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Gələcək girişləri problemsiz etmək üçün bu cihazı xatırla" + }, "deviceApprovalRequired": { "message": "Cihaz təsdiqi tələb olunur. Aşağıdan bir təsdiq variantı seçin:" }, + "deviceApprovalRequiredV2": { + "message": "Cihaz təsdiqi tələb olunur" + }, + "selectAnApprovalOptionBelow": { + "message": "Aşağıdan bir təsdiq seçimi edin" + }, "rememberThisDevice": { "message": "Bu cihazı xatırla" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "İstifadəçi e-poçtu əskikdir" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiv istifadəçi e-poçtu tapılmadı. Hesabınızdan çıxış edilir." + }, "deviceTrusted": { "message": "Cihaz güvənlidir" }, @@ -3521,6 +3506,14 @@ "message": "Hesabınızın kilidini açın, yeni bir pəncərədə açılır", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Vaxt əsaslı Təkistifadəlik Parol Doğrulama Kodu", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Hazırkı TOTP-nin bitməsinə qalan vaxt", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Kimlik məlumatlarını doldur", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Müraciət edilir" }, + "loggedInExclamation": { + "message": "Giriş edildi!" + }, "passkeyNotCopied": { "message": "Keçid açarı kopyalanmır" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtrlər" }, + "filterVault": { + "message": "Seyfi filtrlə" + }, + "filterApplied": { + "message": "Bir filtr tətbiq olundu" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtr tətbiq olundu", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Şəxsi detallar" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Uzantı ikonunda giriş üçün avto-doldurma təklif sayını göstər" }, + "showQuickCopyActions": { + "message": "Seyfdə cəld kopyalama fəaliyyətlərini göstər" + }, "systemDefault": { "message": "İlkin sistem" }, "enterprisePolicyRequirementsApplied": { "message": "Müəssisə siyasət tələbləri bu ayara tətbiq edildi" }, + "sshPrivateKey": { + "message": "Private açar" + }, + "sshPublicKey": { + "message": "Public açar" + }, + "sshFingerprint": { + "message": "Barmaq izi" + }, + "sshKeyAlgorithm": { + "message": "Açar növü" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Yenidən sına" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Bu elementə düzəliş etmə icazəniz yoxdur" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Bitwarden masaüstü tətbiqi bağlı olduğu üçün biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authenticating": { "message": "Kimlik doğrulama" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Parol yarat" + }, + "compactMode": { + "message": "Yığcam rejim" + }, + "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" + }, + "wide": { + "message": "Eni" + }, + "extraWide": { + "message": "Ekstra enli" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 294ffea0563..036b1dfcb24 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -26,7 +26,7 @@ "message": "Log in with passkey" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Выкарыстаць аднаразовы ўваход" }, "welcomeBack": { "message": "З вяртаннем" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Скапіяваць нумар ліцэнзіі" }, + "copyPrivateKey": { + "message": "Скапіяваць прыватны ключ" + }, + "copyPublicKey": { + "message": "Скапіяваць публічны ключ" + }, + "copyFingerprint": { + "message": "Скапіяваць адбітак пальца" + }, "copyCustomField": { "message": "Скапіяваць $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Аўтазапаўненне асабістых даных" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Генерыраваць пароль (з капіяваннем)" }, @@ -438,9 +454,6 @@ "length": { "message": "Даўжыня" }, - "passwordMinLength": { - "message": "Мінімальная даўжыня пароля" - }, "uppercase": { "message": "Вялікія літары (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Мінімум спецыяльных сімвалаў" }, - "avoidAmbChar": { - "message": "Пазбягаць неадназначных сімвалаў", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Пазбягаць неадназначных сімвалаў", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Ацаніць пашырэнне" }, - "rateExtensionDesc": { - "message": "Падумайце пра тое, каб дапамагчы нам добрым водгукам!" - }, "browserNotSupportClipboard": { "message": "Ваш вэб-браўзер не падтрымлівае капіяванне даных у буфер абмену. Скапіюйце іх уручную." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Спіс элементаў пасведчання на старонцы з укладкамі для лёгкага аўтазапаўнення." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Ачыстка буфера абмену", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ПАПЯРЭДЖАННЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Папярэджанне", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Пацвердзіць экспартаванне сховішча" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Перамясціць у арганізацыю" }, - "share": { - "message": "Абагуліць" - }, "movedItemToOrg": { "message": "$ITEMNAME$ перамешчана ў $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Увядзіце 6 лічбаў праверачнага кода з вашай праграмы аўтэнтыфікацыі." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Увядзіце 6 лічбаў праверачнага кода, які быў адпраўлены на $EMAIL$.", "placeholders": { @@ -1450,7 +1466,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Прапановы аўтазапаўнення" }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Калі выяўлена форма ўваходу, то будзе выканана яе аўтазапаўненне падчас загрузкі вэб-старонкі." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Скампраметаваныя або ненадзейныя вэб-сайты могуць задзейнічаць функцыю аўтазапаўнення падчас загрузкі старонкі." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Пасведчанне" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "newItemHeader": { "message": "Новы $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Абароненыя нататкі" }, + "sshKeys": { + "message": "Ключы SSH" + }, "clear": { "message": "Ачысціць", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Кланіраваць" }, - "passwordGeneratorPolicyInEffect": { - "message": "Адна або больш палітык арганізацыі ўплывае на налады генератара." - }, "passwordGenerator": { "message": "Генератар пароляў" }, @@ -2318,6 +2324,9 @@ "message": "Дамены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Выключаныя дамены" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Вэб-сайт $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Падрабязнасці Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Пошук у Send'ах", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Дадаць Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Тэкст" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Дасягнута максімальная колькасць доступаў", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Пратэрмінавана" }, - "pendingDeletion": { - "message": "Чакаецца выдаленне" - }, "passwordProtected": { "message": "Абаронена паролем" }, @@ -2456,24 +2462,9 @@ "message": "Рэдагаваць Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Які гэта тып Send'a?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Зразумелая назва для апісання гэтага Send'a.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Файл, які вы хочаце адправіць." - }, "deletionDate": { "message": "Дата выдалення" }, - "deletionDateDesc": { - "message": "Send будзе незваротна выдалены ў азначаныя дату і час.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Дата завяршэння" }, - "expirationDateDesc": { - "message": "Калі зададзена, то доступ да гэтага Send міне ў азначаную дату і час.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 дзень" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Карыстальніцкі" }, - "maximumAccessCount": { - "message": "Максімальная колькасць доступаў" - }, - "maximumAccessCountDesc": { - "message": "Калі прызначана, то карыстальнікі больш не змогуць атрымаць доступ да гэтага Send пасля таго, як будзе дасягнута максімальная колькасць зваротаў.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Па магчымасці запытваць у карыстальнікаў пароль для доступу да гэтага Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Прыватныя нататкі пра гэты Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Адключыць гэты Send, каб ніхто не змог атрымаць да яго доступ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Скапіяваць спасылку на гэты Send у буфер абмену пасля захавання.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Тэкст, які вы хочаце адправіць." - }, - "sendHideText": { - "message": "Прадвызначана хаваць тэкст гэтага Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Бягучая колькасць доступаў" - }, "createSend": { "message": "Стварыць новы Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Перад тым, як пачаць" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Для выкарыстання каляндарнага стылю выбару даты", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "націсніце тут", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "для адкрыцця ў новым акне.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Азначаная дата завяршэння тэрміну дзеяння з'яўляецца няправільнай." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Адбылася памылка пры захаванні дат выдалення і завяршэння тэрміну дзеяння." }, - "hideEmail": { - "message": "Схаваць мой адрас электроннай пошты ад атрымальнікаў." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Адна або больш палітык арганізацыі ўплываюць на параметры Send." - }, "passwordPrompt": { "message": "Паўторны запыт асноўнага пароля" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Памылка" }, - "regenerateUsername": { - "message": "Паўторна генерыраваць імя карыстальніка" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Генерыраваць імя карыстальніка" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Тып імя карыстальніка" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Адрасы электроннай пошты з плюсам", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Назва вэб-сайта" }, - "whatWouldYouLikeToGenerate": { - "message": "Што вы хочаце генерыраваць?" - }, - "passwordType": { - "message": "Тып пароля" - }, "service": { "message": "Сэрвіс" }, @@ -2932,7 +2890,7 @@ "message": "Генерыраваць псеўданім электроннай пошты са знешнім сэрвісам перасылкі." }, "forwarderDomainName": { - "message": "Дамен электроннай пошты", + "message": "Email domain", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Адправіць апавяшчэнне паўторна" }, - "viewAllLoginOptions": { - "message": "Паглядзець усе варыянты ўваходу" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "Апавяшчэнне было адпраўлена на вашу прыладу." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Ініцыяваны ўваход" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Адкрываць у новым акне" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Патрабуецца ўхваленне прылады. Выберыце параметры ўхвалення ніжэй:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Запомніць гэту прыладу" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Адсутнічае электронная пошта карыстальніка" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Давераная прылада" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4648,11 +4713,11 @@ "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Прагал", + "message": "Space", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Тыльда", + "message": "Tilde", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { @@ -4680,7 +4745,7 @@ "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Карэтка", + "message": "Caret", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4688,27 +4753,27 @@ "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Зорачка", + "message": "Asterisk", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Левая дужка", + "message": "Left parenthesis", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Правая дужка", + "message": "Right parenthesis", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Падкрэсленне", + "message": "Underscore", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Злучок", + "message": "Hyphen", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Плюс", + "message": "Plus", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { @@ -4716,23 +4781,23 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Левая фігурная дужка", + "message": "Left brace", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Правая фігурная дужка", + "message": "Right brace", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Левая квадратная дужка", + "message": "Left bracket", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Правая квадратная дужка", + "message": "Right bracket", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Раздзяляльнік", + "message": "Pipe", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { @@ -4740,39 +4805,39 @@ "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Двукроп'е", + "message": "Colon", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Кропка з коскай", + "message": "Semicolon", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Падвойнае двукоссе", + "message": "Double quote", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Адзінарнае двукоссе", + "message": "Single quote", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Менш", + "message": "Less than", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Больш", + "message": "Greater than", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Коска", + "message": "Comma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Кропка", + "message": "Period", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Пытальнік", + "message": "Question mark", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { @@ -4780,12 +4845,63 @@ "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Малыя літары" + "message": "Lowercase" }, "uppercaseAriaLabel": { - "message": "Вялікія літары" + "message": "Uppercase" }, "generatedPassword": { - "message": "Згенерыраваны пароль" + "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index e8249decb6d..098e2b91051 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Копиране на номера на свидетелството" }, + "copyPrivateKey": { + "message": "Копиране на частния ключ" + }, + "copyPublicKey": { + "message": "Копиране на публичния ключ" + }, + "copyFingerprint": { + "message": "Копиране на отпечатъка" + }, "copyCustomField": { "message": "Копиране на $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Самопопълваща се самоличност" }, + "fillVerificationCode": { + "message": "Попълване на кода за потвърждаване" + }, + "fillVerificationCodeAria": { + "message": "Попълване на кода за потвърждаване", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Генериране на парола (копирана)" }, @@ -438,9 +454,6 @@ "length": { "message": "Дължина" }, - "passwordMinLength": { - "message": "Минимална дължина на паролата" - }, "uppercase": { "message": "Главни букви (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Минимален брой специални знаци" }, - "avoidAmbChar": { - "message": "Без нееднозначни знаци", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Без нееднозначни знаци", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Оценяване на разширението" }, - "rateExtensionDesc": { - "message": "Молим да ни помогнете, като оставите положителен отзив!" - }, "browserNotSupportClipboard": { "message": "Браузърът не поддържа копиране в буфера, затова копирайте на ръка." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Показване на самоличностите в страницата с разделите, за лесно автоматично попълване." }, + "clickToAutofillOnVault": { + "message": "Щракнете върху елементите в трезора за автоматично попълване" + }, "clearClipboard": { "message": "Изчистване на буфера", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ВНИМАНИЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Внимание", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Потвърждаване на изнасянето на трезора" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Преместване в организация" }, - "share": { - "message": "Споделяне" - }, "movedItemToOrg": { "message": "$ITEMNAME$ се премести в $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Въведете шестцифрения код за потвърждение от приложението за удостоверяване." }, + "authenticationTimeout": { + "message": "Време на давност за удостоверяването" + }, + "authenticationSessionTimedOut": { + "message": "Сесията за удостоверяване е изтекла. Моля, започнете отначало процеса по вписване." + }, "enterVerificationCodeEmail": { "message": "Въведете шестцифрения код за потвърждение, който е бил изпратен на $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "При засичане на формуляр за вписване при зареждането на уеб страницата автоматично да се попълват данните на съответстващата регистрация." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Внимание:$CLOSETAG$ Опасните или компроментирани уеб сайтове могат да се възползват от функционалността за автоматично попълване при зареждане на страницата.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Компроментирани и измамни уеб сайтове могат да се възползват от автоматичното попълване при зареждане на страницата." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Самоличност" }, + "typeSshKey": { + "message": "SSH ключ" + }, "newItemHeader": { "message": "Ново $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Защитени бележки" }, + "sshKeys": { + "message": "SSH ключове" + }, "clear": { "message": "Изчистване", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Клониране" }, - "passwordGeneratorPolicyInEffect": { - "message": "Поне една политика на организация влияе на настройките на генерирането на паролите." - }, "passwordGenerator": { "message": "Генератор на пароли" }, @@ -2318,6 +2324,9 @@ "message": "Домейни", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Блокирани домейни" + }, "excludedDomains": { "message": "Изключени домейни" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Битуорден няма да пита дали да запазва данните за вход в тези сайтове за всички регистрации, в които сте вписан(а). За да влезе правилото в сила, презаредете страницата." }, + "blockedDomainsDesc": { + "message": "Автоматичното попълване и други свързани функции няма да бъдат предлагани за тези уеб сайтове. Трябва да презаредите страницата, за да влязат в сила промените." + }, + "autofillBlockedNotice": { + "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате и промените това в настройките." + }, + "autofillBlockedTooltip": { + "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате това в настройките." + }, "websiteItemLabel": { "message": "Уеб сайт $number$ (адрес)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Промените на блокираните домейни са запазени" + }, "excludedDomainsSavedSuccess": { "message": "Промените на изключените домейни са запазени" }, @@ -2373,14 +2394,6 @@ "message": "Подробности за Изпращането", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Търсене в изпратените", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Добавяне на изпращане", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Текст" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Скриване на текста по подразбиране" }, - "maxAccessCountReached": { - "message": "Достигнат е максималният брой достъпвания", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Изтекъл" }, - "pendingDeletion": { - "message": "Предстои изтриване" - }, "passwordProtected": { "message": "Защита с парола" }, @@ -2456,24 +2462,9 @@ "message": "Редактиране на изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Вид на изпратеното", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Описателно име за това изпращане.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Файл за изпращане." - }, "deletionDate": { "message": "Дата на изтриване" }, - "deletionDateDesc": { - "message": "Изпращането ще бъде окончателно изтрито на зададената дата и време.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Изпращането ще бъде окончателно изтрито на тази дата.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Срок на валидност" }, - "expirationDateDesc": { - "message": "При задаване — това изпращане ще се изключи на зададената дата и време.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 ден" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "По избор" }, - "maximumAccessCount": { - "message": "Максимален брой достъпвания." - }, - "maximumAccessCountDesc": { - "message": "При задаване — това изпращане ще се изключи след определен брой достъпвания.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Изискване на парола за достъп до това изпращане.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Добавете незадължителна парола, с която получателите да имат достъп до това Изпращане.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Скрити бележки за това изпращане.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Пълно спиране на това изпращане — никой няма да има достъп.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Копиране на връзката към това изпращане при запазването му.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст за изпращане." - }, - "sendHideText": { - "message": "Стандартно текстът на това изпращане да се крие.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Текущ брой на достъпванията" - }, "createSend": { "message": "Създаване на изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Преди да почнете" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "За избор на дата от каландар", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "натиснете тук", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "за изскачащ прозорец.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Неправилна дата на валидност." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Грешка при запазване на датата на валидност и изтриване." }, - "hideEmail": { - "message": "Скриване на е-пощата ми от получателите." - }, "hideYourEmail": { "message": "Скриване на Вашата е-поща от получателите." }, - "sendOptionsPolicyInEffect": { - "message": "Поне една политика на организация влияе на настройките за изпращане." - }, "passwordPrompt": { "message": "Повторно запитване за главната парола" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Грешка" }, - "regenerateUsername": { - "message": "Повторно генериране на потр. име" + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Генериране на потр. име" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Генериране на електронна поща" }, - "generatorBoundariesHint": { - "message": "Стойността трябва да бъде между $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Стойността трябва да бъде между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Тип потребителско име" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Адрес на е-поща с плюс", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Име на уеб сайт" }, - "whatWouldYouLikeToGenerate": { - "message": "Какво бихте искали да генерирате?" - }, - "passwordType": { - "message": "Тип парола" - }, "service": { "message": "Услуга" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Повторно изпращане на известието" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Вижте всички възможности за вписване" + }, + "viewAllLoginOptionsV1": { "message": "Вижте всички възможности за вписване" }, "notificationSentDevice": { "message": "Към устройството Ви е изпратено известие." }, + "aNotificationWasSentToYourDevice": { + "message": "Към устройството Ви е изпратено известие" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Уверете се, че регистрацията Ви е отключена и че уникалната фраза съвпада с другото устройство" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Ще получите уведомление когато заявката бъде одобрена" + }, + "needAnotherOptionV1": { + "message": "Предпочитате друг вариант?" + }, "loginInitiated": { "message": "Вписването е стартирано" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Отваря се в нов прозорец" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запомняне на това устройство, така че в бъдеще вписването да бъде по-лесно" + }, "deviceApprovalRequired": { "message": "Изисква се одобрение на устройството. Изберете начин за одобрение по-долу:" }, + "deviceApprovalRequiredV2": { + "message": "Необходимо е одобрение на устройството" + }, + "selectAnApprovalOptionBelow": { + "message": "Изберете начин за одобряване по-долу" + }, "rememberThisDevice": { "message": "Запомняне на това устройство" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Липсва е-поща на потребителя" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Не е намерена е-поща на активен потребител. Ще бъдете отписан(а)." + }, "deviceTrusted": { "message": "Устройството е доверено" }, @@ -3521,6 +3506,14 @@ "message": "Отклюване на регистрацията, отваря се в нов прозорец", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Код за потвърждение на еднократната времево-ограничена парола", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Оставащо време преди изтичането на текущия код", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Попълване на данните за", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Използване на" }, + "loggedInExclamation": { + "message": "Вписахте се!" + }, "passkeyNotCopied": { "message": "Секретният ключ няма да бъде копиран" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Филтри" }, + "filterVault": { + "message": "Филтриране на трезора" + }, + "filterApplied": { + "message": "Приложен е един филтър" + }, + "filterAppliedPlural": { + "message": "Приложени са $COUNT$ филтри", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Лични данни" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Показване на броя предложения за автоматично попълване на данни за вписване върху иконката на добавката" }, + "showQuickCopyActions": { + "message": "Показване на действията за бързо копиране в трезора" + }, "systemDefault": { "message": "По подразбиране за системата" }, "enterprisePolicyRequirementsApplied": { "message": "Изискванията на политиката за големи компании бяха приложени към тази настройка" }, + "sshPrivateKey": { + "message": "Частен ключ" + }, + "sshPublicKey": { + "message": "Публичен ключ" + }, + "sshFingerprint": { + "message": "Отпечатък" + }, + "sshKeyAlgorithm": { + "message": "Тип на ключа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 бита" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 бита" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 бита" + }, "retry": { "message": "Повторен опит" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Нямате право за редактиране на този елемент" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Отключването с биометрични данни не е налично, тъй като приложението на Биуорден за компютър не работи." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authenticating": { "message": "Удостоверяване" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Генерирана парола" + }, + "compactMode": { + "message": "Компактен режим" + }, + "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": "Ширина на разширението" + }, + "wide": { + "message": "Широко" + }, + "extraWide": { + "message": "Много широко" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index c05fd642e79..f60fc2c9683 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "পাসওয়ার্ড তৈরি করুন (অনুলিপিকৃত)" }, @@ -438,9 +454,6 @@ "length": { "message": "দৈর্ঘ্য" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "ন্যূনতম বিশেষ" }, - "avoidAmbChar": { - "message": "অস্পষ্ট বর্ণগুলি এড়িয়ে চলুন", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "এক্সটেনশনটি মূল্যায়ন করুন" }, - "rateExtensionDesc": { - "message": "দয়া করে একটি ভাল পর্যালোচনার মাধ্যমে সাহায্য করতে আমাদের বিবেচনা করুন!" - }, "browserNotSupportClipboard": { "message": "আপনার ওয়েব ব্রাউজার সহজে ক্লিপবোর্ড অনুলিপি সমর্থন করে না। পরিবর্তে এটি নিজেই অনুলিপি করুন।" }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "ক্লিপবোর্ড পরিষ্কার", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "সতর্কতা", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "ভল্ট রফতানির নিশ্চয়তা দিন" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "ভাগ করুন" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "আপনার প্রমাণীকরণকারী অ্যাপ থেকে ৬ সংখ্যার যাচাইকরণ কোডটি প্রবেশ করুন।" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ এ ইমেইল করা ৬ সংখ্যার যাচাই কোডটি প্রবেশ করুন।", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "যদি কোনও লগইন ফর্ম সনাক্ত হয়, ওয়েব পৃষ্ঠাটি লোড হওয়ার পরে স্বয়ংক্রিয়ভাবে স্বতঃপূরণ করুন।" }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "পরিচয়" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "সুরক্ষিত নোট" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "পরিষ্কার", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "নকল" }, - "passwordGeneratorPolicyInEffect": { - "message": "এক বা একাধিক সংস্থার নীতিগুলি আপনার উৎপাদকের সেটিংসকে প্রভাবিত করছে।" - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "অবসায়িত" }, - "pendingDeletion": { - "message": "মুছে ফেলার জন্য অপেক্ষমান" - }, "passwordProtected": { "message": "পাসওয়ার্ড সুরক্ষিত" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index bc8d843045e..aaf04da98b7 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Isteklo" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dan" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index d3cdc23d8c5..e7113d5aab1 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -120,7 +120,7 @@ "message": "Copia contrasenya" }, "copyPassphrase": { - "message": "Copia clau de pas" + "message": "Copy passphrase" }, "copyNote": { "message": "Copia nota" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copia el número de llicència" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copia $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Emplena automàticament l'identitat" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Genera contrasenya (copiada)" }, @@ -427,7 +443,7 @@ "message": "Genera contrasenya" }, "generatePassphrase": { - "message": "Genera clau de pas" + "message": "Generate passphrase" }, "regeneratePassword": { "message": "Regenera contrasenya" @@ -438,9 +454,6 @@ "length": { "message": "Longitud" }, - "passwordMinLength": { - "message": "Longitud mínima de la contrasenya" - }, "uppercase": { "message": "Majúscula (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mínim de caràcters especials" }, - "avoidAmbChar": { - "message": "Eviteu caràcters ambigus", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Eviteu caràcters ambigus", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Valora aquesta extensió" }, - "rateExtensionDesc": { - "message": "Considereu ajudar-nos amb una bona valoració!" - }, "browserNotSupportClipboard": { "message": "El vostre navegador web no admet la còpia fàcil del porta-retalls. Copieu-ho manualment." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Llista els elements d'identitat de la pestanya de la pàgina per facilitar l'autoemplenat." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Buida el porta-retalls", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ADVERTIMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirma l'exportació de la Caixa forta" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Desplaça a l'organització" }, - "share": { - "message": "Comparteix" - }, "movedItemToOrg": { "message": "$ITEMNAME$ desplaçat a $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Introduïu el codi de verificació de 6 dígits de l'aplicació autenticadora." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Introduïu el codi de verificació de 6 dígits que s'ha enviat per correu electrònic a $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Si es detecta un formulari d'inici de sessió, es realitza automàticament un emplenament automàtic quan es carrega la pàgina web." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Els llocs web compromesos o no fiables poden aprofitar-se de l'emplenament automàtic en carregar de la pàgina." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitat" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Notes segures" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Esborra", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clona" }, - "passwordGeneratorPolicyInEffect": { - "message": "Una o més polítiques d’organització afecten la configuració del generador." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Dominis", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominis exclosos" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no demanarà que es guarden les dades d'inici de sessió d'aquests dominis per a tots els comptes iniciats. Heu d'actualitzar la pàgina perquè els canvis tinguen efecte." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Detalls de l'enviament", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Cerca Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Afig Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Ocultar el text per defecte" }, - "maxAccessCountReached": { - "message": "S'ha assolit el recompte màxim d'accesos", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Caducat" }, - "pendingDeletion": { - "message": "Pendent de supressió" - }, "passwordProtected": { "message": "Protegit amb contrasenya" }, @@ -2456,24 +2462,9 @@ "message": "Edita Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Quin tipus de Send és aquest?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nom apropiat per descriure aquest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "El fitxer que voleu enviar." - }, "deletionDate": { "message": "Data de supressió" }, - "deletionDateDesc": { - "message": "L'enviament se suprimirà permanentment a la data i hora especificades.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "L'enviament s'esborrarà permanentment en aquesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Data de caducitat" }, - "expirationDateDesc": { - "message": "Si s'estableix, l'accés a aquest enviament caducarà en la data i hora especificades.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dia" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalitzat" }, - "maximumAccessCount": { - "message": "Recompte màxim d'accessos" - }, - "maximumAccessCountDesc": { - "message": "Si s’estableix, els usuaris ja no podran accedir a aquest Send una vegada s’assolisca el nombre màxim d’accessos.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Opcionalment, necessiteu una contrasenya perquè els usuaris accedisquen a aquest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Notes privades sobre aquest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Desactiveu aquest Send perquè ningú no hi puga accedir.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copieu l'enllaç d'aquest Send al porta-retalls després de guardar-lo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "El text que voleu enviar." - }, - "sendHideText": { - "message": "Amaga el text d'aquest Send per defecte.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Recompte d’accessos actual" - }, "createSend": { "message": "Crea un nou Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Abans de començar" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Per utilitzar un selector de dates d'estil calendari", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "feu clic ací", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "per eixir de la finestra.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "La data de caducitat proporcionada no és vàlida." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "S'ha produït un error en guardar les dates de supressió i caducitat." }, - "hideEmail": { - "message": "Amagueu la meua adreça de correu electrònic als destinataris." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Una o més polítiques d'organització afecten les vostres opcions del Send." - }, "passwordPrompt": { "message": "Sol·licitud de la contrasenya mestra" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenera el nom d'usuari" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Genera un nom d'usuari" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tipus de nom d'usuari" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Adreça amb sufix", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nom del lloc web" }, - "whatWouldYouLikeToGenerate": { - "message": "Què voleu generar?" - }, - "passwordType": { - "message": "Tipus de contrasenya" - }, "service": { "message": "Servei" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Torna a enviar la notificació" }, - "viewAllLoginOptions": { - "message": "Veure totes les opcions d'inici de sessió" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "S'ha enviat una notificació al vostre dispositiu." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "S'ha iniciat la sessió" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "S'obri en una finestra nova" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Cal l'aprovació del dispositiu. Seleccioneu una opció d'aprovació a continuació:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Recorda aquest dispositiu" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Falta el correu electrònic de l'usuari" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositiu de confiança" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Ompliu les credencials per a", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "La clau de pas no es copiarà" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtres" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Detalls personals" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 9943cbebc12..eb2f9149daf 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopírovat číslo dokladu totožnosti" }, + "copyPrivateKey": { + "message": "Kopírovat soukromý klíč" + }, + "copyPublicKey": { + "message": "Kopírovat veřejný klíč" + }, + "copyFingerprint": { + "message": "Kopírovat otisk prstu" + }, "copyCustomField": { "message": "Kopírovat $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Automaticky vyplnit identitu" }, + "fillVerificationCode": { + "message": "Vyplnit ověřovací kód" + }, + "fillVerificationCodeAria": { + "message": "Vyplnit ověřovací kód", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Vygenerovat heslo a zkopírovat do schránky" }, @@ -438,9 +454,6 @@ "length": { "message": "Délka" }, - "passwordMinLength": { - "message": "Minimální délka hesla" - }, "uppercase": { "message": "Velká písmena (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimální počet speciálních znaků" }, - "avoidAmbChar": { - "message": "Nepoužívat zaměnitelné znaky", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Nepoužívat zaměnitelné znaky", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Ohodnotit rozšíření" }, - "rateExtensionDesc": { - "message": "Pomozte nám napsáním dobré recenze!" - }, "browserNotSupportClipboard": { "message": "Váš webový prohlížeč nepodporuje automatické kopírování do schránky. Musíte ho zkopírovat ručně." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Pro snadné vyplnění zobrazí položky identit na obrazovce Karta." }, + "clickToAutofillOnVault": { + "message": "Klepněte na položky pro automatické vyplnění v zobrazení trezoru" + }, "clearClipboard": { "message": "Vymazat schránku", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "VAROVÁNÍ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Varování", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Potvrdit export trezoru" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Přesunout do organizace" }, - "share": { - "message": "Sdílet" - }, "movedItemToOrg": { "message": "$ITEMNAME$ přesunut do $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Zadejte 6místný kód z ověřovací aplikace." }, + "authenticationTimeout": { + "message": "Časový limit ověření" + }, + "authenticationSessionTimedOut": { + "message": "Vypršel časový limit relace ověřování. Restartujte proces přihlášení." + }, "enterVerificationCodeEmail": { "message": "Zadejte 6místný kód z e-mailu, který byl zaslán na $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Pokud je zjištěn přihlašovací formulář, automaticky se při načítání webové stránky vyplní přihlašovací údaje." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Varování:$CLOSETAG$ Kompromitované nebo nedůvěryhodné webové stránky mohou využívat automatické vyplňování při načítání stránky.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Kompromitované nebo nedůvěryhodné webové stránky mohou zneužívat automatické vyplňování při načítání stránky." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identita" }, + "typeSshKey": { + "message": "SSH klíč" + }, "newItemHeader": { "message": "Nové $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Zabezpečené poznámky" }, + "sshKeys": { + "message": "SSH klíče" + }, "clear": { "message": "Vymazat", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Duplikovat" }, - "passwordGeneratorPolicyInEffect": { - "message": "Jedna nebo více zásad organizace ovlivňují nastavení generátoru." - }, "passwordGenerator": { "message": "Generátor hesla" }, @@ -2318,6 +2324,9 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, "excludedDomains": { "message": "Vyloučené domény" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude žádat o uložení přihlašovacích údajů pro tyto domény pro všechny přihlášené účty. Aby se změny projevily, musíte stránku obnovit." }, + "blockedDomainsDesc": { + "message": "Automatické vyplňování a další související funkce nebudou pro tyto webové stránky nabízeny. Aby se změny projevily, musíte stránku aktualizovat." + }, + "autofillBlockedNotice": { + "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to nebo to změňte v nastavení." + }, + "autofillBlockedTooltip": { + "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to v nastavení." + }, "websiteItemLabel": { "message": "Webová stránka $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Změny v zablokovaných doménách byly uloženy" + }, "excludedDomainsSavedSuccess": { "message": "Vyloučené změny domény byly uloženy" }, @@ -2373,14 +2394,6 @@ "message": "Podrobnosti Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Prohledat Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Přidat Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Ve výchozím nastavení skrýt text" }, - "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." - }, "expired": { "message": "Vypršela platnost" }, - "pendingDeletion": { - "message": "Čekání na smazání" - }, "passwordProtected": { "message": "Chráněno heslem" }, @@ -2456,24 +2462,9 @@ "message": "Upravit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Jakého typu je tento Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Přátelský název pro popis tohoto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Soubor, který chcete odeslat." - }, "deletionDate": { "message": "Datum smazání" }, - "deletionDateDesc": { - "message": "Tento Send bude trvale smazán v určený datum a čas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Tento Send bude trvale smazán v určené datum.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Datum vypršení platnosti" }, - "expirationDateDesc": { - "message": "Je-li nastaveno, přístup k tomuto Send vyprší v daný datum a čas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 den" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Vlastní" }, - "maximumAccessCount": { - "message": "Maximální počet přístupů" - }, - "maximumAccessCountDesc": { - "message": "Je-li nastaveno, uživatelé již nebudou mít přístup k tomuto Send, jakmile bude dosaženo maximálního počtu přístupů.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Volitelně vyžadovat heslo pro přístup k tomuto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Přidá volitelné heslo pro příjemce pro přístup k tomuto Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Soukromé poznámky o tomto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deaktivuje tento Send, díky čemuž k němu nebude moci nikdo přistoupit.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Zkopíruje odkaz pro sdílení tohoto Send po uložení.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Text, který chcete odeslat." - }, - "sendHideText": { - "message": "Skrýt ve výchozím stavu text tohoto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuální počet přístupů" - }, "createSend": { "message": "Nový Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Než začnete" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Chcete-li použít k výběru data styl kalendáře", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klepněte zde", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "pro zobrazení okna.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Uvedené datum vypršení platnosti není platné." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Došlo k chybě při ukládání datumu smzání a vypršení platnosti." }, - "hideEmail": { - "message": "Skrýt mou e-mailovou adresu před příjemci." - }, "hideYourEmail": { "message": "Skryje Vaši e-mailovou adresu před zobrazením." }, - "sendOptionsPolicyInEffect": { - "message": "Jedna nebo více zásad organizace ovlivňuje nastavení Send." - }, "passwordPrompt": { "message": "Zeptat se znovu na hlavní heslo" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Chyba" }, - "regenerateUsername": { - "message": "Znovu vygenerovat uživatelské jméno" + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Vygenerovat uživatelské jméno" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Vygenerovat e-mail" }, - "generatorBoundariesHint": { - "message": "Hodnota musí být mezi $MIN$ a $MAX$", + "spinboxBoundariesHint": { + "message": "Hodnota musí být mezi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Typ uživatelského jména" + "passwordLengthRecommendationHint": { + "message": " Použijte $RECOMMENDED$ nebo více znaků k vytvoření silného hesla.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Použijte $RECOMMENDED$ slov nebo více slov k vytvoření silné heslové fráze.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-mailová adresa s plusem", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Název webu" }, - "whatWouldYouLikeToGenerate": { - "message": "Co chcete vygenerovat?" - }, - "passwordType": { - "message": "Typ hesla" - }, "service": { "message": "Služba" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Znovu odeslat oznámení" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Zobrazit všechny volby přihlášení" + }, + "viewAllLoginOptionsV1": { "message": "Zobrazit všechny volby přihlášení" }, "notificationSentDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení." }, + "aNotificationWasSentToYourDevice": { + "message": "Na Vaše zařízení bylo odesláno oznámení" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Ujistěte se, že je Váš trezor odemčen a fráze otisku prstu se shodují s druhým zařízením" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Budete upozorněni, jakmile bude žádost schválena" + }, + "needAnotherOptionV1": { + "message": "Potřebujete další volby?" + }, "loginInitiated": { "message": "Bylo zahájeno přihlášení" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Otevře se v novém okně" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamatovat si toto zařízení pro bezproblémové budoucí přihlášení" + }, "deviceApprovalRequired": { "message": "Vyžaduje se schválení zařízení. Vyberte možnost schválení níže:" }, + "deviceApprovalRequiredV2": { + "message": "Vyžaduje se schválení zařízení" + }, + "selectAnApprovalOptionBelow": { + "message": "Vyberte volbu schválení níže" + }, "rememberThisDevice": { "message": "Zapamatovat toto zařízení" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Chybí e-mail uživatele" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktivní uživatelský e-mail nebyl nalezen. Budete odhlášeni." + }, "deviceTrusted": { "message": "Zařízení zařazeno mezi důvěryhodné" }, @@ -3521,6 +3506,14 @@ "message": "Odemknout účet, otevře se v novém okně", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Ověřovací kód TOTP", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Zbývající čas před vypršením aktuálního TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Vyplnit přihlašovací údaje pro", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Přistupování" }, + "loggedInExclamation": { + "message": "Přihlášeno!" + }, "passkeyNotCopied": { "message": "Přístupový klíč nebude zkopírován" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtry" }, + "filterVault": { + "message": "Filtrovat trezor" + }, + "filterApplied": { + "message": "Byl použit jeden filtr" + }, + "filterAppliedPlural": { + "message": "Bylo použito $COUNT$ filtrů", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Osobní údaje" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Zobrazit počet návrhů automatického vyplňování přihlášení na ikoně rozšíření" }, + "showQuickCopyActions": { + "message": "Zobrazit akce rychlé kopie v trezoru" + }, "systemDefault": { "message": "Systémový výchozí" }, "enterprisePolicyRequirementsApplied": { "message": "Na toto nastavení byly uplatněny požadavky podnikových zásad" }, + "sshPrivateKey": { + "message": "Soukromý klíč" + }, + "sshPublicKey": { + "message": "Veřejný klíč" + }, + "sshFingerprint": { + "message": "Otisk prstu" + }, + "sshKeyAlgorithm": { + "message": "Typ klíče" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 bitový" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 bitový" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 bitový" + }, "retry": { "message": "Opakovat" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Nemáte oprávnění upravit tuto položku" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrické odemknutí není dostupné, protože je aplikace Bitwarden zavřena." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrické odemknutí není dostupné, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authenticating": { "message": "Ověřování" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Vygenerované heslo" + }, + "compactMode": { + "message": "Kompaktní režim" + }, + "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í" + }, + "wide": { + "message": "Šířka" + }, + "extraWide": { + "message": "Extra široký" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 5bec66dec95..8ad494f0bfb 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Llenwi hunaniaeth" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Cynhyrchu cyfrinair (wedi'i gopïo)" }, @@ -438,9 +454,6 @@ "length": { "message": "Hyd" }, - "passwordMinLength": { - "message": "Hyd lleiaf cyfrineiriau" - }, "uppercase": { "message": "Priflythrennau (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Isafswm nodau arbennig" }, - "avoidAmbChar": { - "message": "Osgoi nodau amwys", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rhoi eich barn ar yr estyniad" }, - "rateExtensionDesc": { - "message": "Ystyriwch ein helpu ni gydag adolygiad da!" - }, "browserNotSupportClipboard": { "message": "Dyw eich porwr gwe ddim yn cefnogi copïo drwy'r clipfwrdd yn hawdd. Copïwch â llaw yn lle." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clirio'r clipfwrdd", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "RHYBUDD", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Rhannu" - }, "movedItemToOrg": { "message": "Symudwyd $ITEMNAME$ i $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Llenwi'n awtomatig wrth i dudalen lwytho os canfyddir ffurflen mewngofnodi." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Hunaniaeth" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Nodiadau diogel" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clirio", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Parthau wedi'u heithrio" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Chwilio drwy Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Ychwanegu Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Testun" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Wedi dod i ben" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Dyddiad dileu" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Dyddiad dod i ben" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 diwrnod" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Addasedig" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Cyn i chi ddechrau" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "cliciwch yma", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Dyw'r dyddiad dod i ben a roddwyd ddim yn ddilys." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Cuddio fy nghyfeiriad ebost rhag derbynwyr." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Ailofyn am y prif gyfrinair" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Gwall" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Cynhyrchu enw defnyddiwr" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Math o enw defnyddiwr" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "Beth hoffech chi ei gynhyrchu?" - }, - "passwordType": { - "message": "Math o gyfrinair" - }, "service": { "message": "Gwasanaeth" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { - "message": "Gweld pob dewis mewngofnodi" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Cofio'r ddyfais hon" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 60949c317aa..9008049e1a4 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopier licensnummer" }, + "copyPrivateKey": { + "message": "Kopiér privat nøgle" + }, + "copyPublicKey": { + "message": "Kopiér offentlig nøgle" + }, + "copyFingerprint": { + "message": "Kopiér fingeraftryk" + }, "copyCustomField": { "message": "Kopiér $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autoudfyld identitet" }, + "fillVerificationCode": { + "message": "Udfyld bekræftelseskode" + }, + "fillVerificationCodeAria": { + "message": "Udfyld bekræftelseskode", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generér adgangskode (kopieret)" }, @@ -438,9 +454,6 @@ "length": { "message": "Længde" }, - "passwordMinLength": { - "message": "Minimumslængde på adgangskode" - }, "uppercase": { "message": "Store bogstaver (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mindste antal specialtegn" }, - "avoidAmbChar": { - "message": "Undgå tvetydige tegn", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Undgå tvetydige tegn", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Bedøm udvidelsen" }, - "rateExtensionDesc": { - "message": "Overvej om du vil hjælpe os med en god anmeldelse!" - }, "browserNotSupportClipboard": { "message": "Din webbrowser understøtter ikke udklipsholder kopiering. Kopiér det manuelt i stedet." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Vis identitetsemner på siden Fane for nem autoudfyldning." }, + "clickToAutofillOnVault": { + "message": "Klik på emner for at autoudfylde i Boks-visning" + }, "clearClipboard": { "message": "Ryd udklipsholder", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ADVARSEL", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Advarsel", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Bekræft eksport af boks" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Flyt til organisation" }, - "share": { - "message": "Del" - }, "movedItemToOrg": { "message": "$ITEMNAME$ flyttet til $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Indtast den 6-cifrede verifikationskode fra din autentificerings-app." }, + "authenticationTimeout": { + "message": "Godkendelsestimeout" + }, + "authenticationSessionTimedOut": { + "message": "Godkendelsessessionen fik timeout. Genstart loginprocessen." + }, "enterVerificationCodeEmail": { "message": "Indtast den 6-cifrede verifikationskode, der blev sendt til $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Registreres en loginformular, autoudfyld når websiden indlæses." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Advarsel:$CLOSETAG$ Kompromitterede eller ikke-betroede websteder kan misbruge autofyld ved sideindlæsning.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Kompromitterede eller ikke-betroede websteder kan misbruge autoudfyldning ved sideindlæsning." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitet" }, + "typeSshKey": { + "message": "SSH-nøgle" + }, "newItemHeader": { "message": "Ny $TYPE$", "placeholders": { @@ -1801,7 +1807,7 @@ "message": "Ryd generatorhistorik" }, "cleargGeneratorHistoryDescription": { - "message": "Fortsættes, slettes alle poster permanent fra generatorens historik. Sikker på, at handlingen skal udføres?" + "message": "Fortsætter man, slettes alle poster permanent fra generatorens historik. Sikker på, at handlingen skal udføres?" }, "back": { "message": "Tilbage" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Sikre notater" }, + "sshKeys": { + "message": "SSH-nøgler" + }, "clear": { "message": "Ryd", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Klon" }, - "passwordGeneratorPolicyInEffect": { - "message": "Én eller flere organisationspolitikker påvirker dine generatorindstillinger." - }, "passwordGenerator": { "message": "Adgangskodegenerator" }, @@ -2318,6 +2324,9 @@ "message": "Domæner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokerede domæner" + }, "excludedDomains": { "message": "Ekskluderede domæner" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden vil ikke anmode om at gemme login-detaljer for disse domæner for alle indloggede konti. Siden skal opfriskes for at effektuere ændringerne." }, + "blockedDomainsDesc": { + "message": "Autofyldning og andre relaterede funktioner tilbydes ikke på disse websteder. Siden skal opdateres for at effektuere ændringerne." + }, + "autofillBlockedNotice": { + "message": "Autoudfyldning er blokeret på dette websted. Gennemgå eller ændr dette i Indstillinger." + }, + "autofillBlockedTooltip": { + "message": "Autoudfyldning er blokeret på dette websted. Gennemgå i Indstillinger." + }, "websiteItemLabel": { "message": "Websted $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blokeret domæne-ændringer gemt" + }, "excludedDomainsSavedSuccess": { "message": "Ekskluderet domæne-ændringer gemt" }, @@ -2373,14 +2394,6 @@ "message": "Send-detaljer", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Søg i Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Tilføj Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Skjul tekst som standard" }, - "maxAccessCountReached": { - "message": "Maksimalt adgangsantal nået", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Udløbet" }, - "pendingDeletion": { - "message": "Afventer sletning" - }, "passwordProtected": { "message": "Kodeordsbeskyttet" }, @@ -2456,24 +2462,9 @@ "message": "Redigér Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Hvilken type Send er denne?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Et venligt navn til at beskrive denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Den fil, du vil sende." - }, "deletionDate": { "message": "Sletningsdato" }, - "deletionDateDesc": { - "message": "Send'en slettes permanent på den angivne dato og tid.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Denne Send slettes permanent på denne dato.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Udløbsdato" }, - "expirationDateDesc": { - "message": "Hvis angivet, vil adgangen til denne Send udløbe på den angivne dato og tidspunkt.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dag" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Tilpasset" }, - "maximumAccessCount": { - "message": "Maksimalt antal tilgange" - }, - "maximumAccessCountDesc": { - "message": "Hvis opsat, vil brugere ikke længere kunne tilgå denne Send, når det maksimale adgangsantal er nået.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Valgfrit brugeradgangskodekrav for at tilgå denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Tilføj en valgfri adgangskode til modtagere for adgang til denne Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notater om denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deaktivér denne Send, så ingen kan tilgå den.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopiér denne Sends link til udklipsholderen når du gemmer.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Den tekst, du vil sende." - }, - "sendHideText": { - "message": "Skjul denne Sends tekst som standard.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuelt antal tilgange" - }, "createSend": { "message": "Ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Før du starter" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "For at bruge en datovælger i kalenderstil skal du", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klikke her", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "for at åbne dit vindue.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Den angivne udløbsdato er ikke gyldig." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Der opstod en fejl under forsøget på at gemme dine sletnings- og udløbsdatoer." }, - "hideEmail": { - "message": "Skjul min e-mailadresse for modtagere." - }, "hideYourEmail": { "message": "Skjul e-mailadressen for modtagere." }, - "sendOptionsPolicyInEffect": { - "message": "Én eller flere organisationspolitikker påvirker dine Send-valgmuligheder." - }, "passwordPrompt": { "message": "Genanmodning om hovedadgangskode" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Fejl" }, - "regenerateUsername": { - "message": "Regenerér brugernavn" + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generér brugernavn" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generér e-mail" }, - "generatorBoundariesHint": { - "message": "Værdi skal være mellem $MIN$ og $MAX$", + "spinboxBoundariesHint": { + "message": "Værdi skal være mellem $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Brugernavnstype" + "passwordLengthRecommendationHint": { + "message": " Brug minimum $RECOMMENDED$ tegn for at generere en stærk adgangskode.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Brug minimum $RECOMMENDED$ ord for at generere en stærk adgangssætning.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus-adresseret e-mail", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Hjemmeside navn" }, - "whatWouldYouLikeToGenerate": { - "message": "Hvad vil du gerne generere?" - }, - "passwordType": { - "message": "Adgangskodetype" - }, "service": { "message": "Tjeneste" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Gensend notifikation" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Vis alle indlogningsmuligheder" + }, + "viewAllLoginOptionsV1": { "message": "Vis alle indlogningsmuligheder" }, "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." }, + "aNotificationWasSentToYourDevice": { + "message": "En notifikation er sendt til enheden" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Sørg for, at boksen er oplåst, samt at fingeraftrykssætningen matcher på den anden enhed" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Man vil få besked, når anmodningen er godkendt" + }, + "needAnotherOptionV1": { + "message": "Behov for en anden mulighed?" + }, "loginInitiated": { "message": "Indlogning påbegyndt" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Åbnes i et nyt vindue" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Husk denne enhed for at gøre fremtidige indlogninger gnidningsløse" + }, "deviceApprovalRequired": { "message": "Enhedsgodkendelse kræves. Vælg en godkendelsesmulighed nedenfor:" }, + "deviceApprovalRequiredV2": { + "message": "Enhedsgodkendelse kræves" + }, + "selectAnApprovalOptionBelow": { + "message": "Vælg en godkendelsesmulighed nedenfor" + }, "rememberThisDevice": { "message": "Husk denne enhed" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Brugers e-mail mangler" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiv bruger e-mail ikke fundet. Man logges ud." + }, "deviceTrusted": { "message": "Enhed betroet" }, @@ -3521,6 +3506,14 @@ "message": "Oplås kontoen, åbnes i et nyt vindue", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Tidsbaseret engangs adgangskodebekræftelseskode", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Resterende tid før udløb af aktuel TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Angiv legitimationsoplysninger for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Tilgår" }, + "loggedInExclamation": { + "message": "Logget ind!" + }, "passkeyNotCopied": { "message": "Adgangsnøglen kopieres ikke" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtre" }, + "filterVault": { + "message": "Filtrér boks" + }, + "filterApplied": { + "message": "Ét filter anvendt" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtre anvendt", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personlige oplysninger" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Vis antal login-autoudfyldningsforslag på udvidelsesikon" }, + "showQuickCopyActions": { + "message": "Vis hurtig-kopihandlinger på Boks" + }, "systemDefault": { "message": "Systemstandard" }, "enterprisePolicyRequirementsApplied": { "message": "Virksomhedspolitikkrav er anvendt på denne indstilling" }, + "sshPrivateKey": { + "message": "Privat nøgle" + }, + "sshPublicKey": { + "message": "Offentlig nøgle" + }, + "sshFingerprint": { + "message": "Fingeraftryk" + }, + "sshKeyAlgorithm": { + "message": "Nøgletype" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Forsøg igen" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Ingen tilladelse til at redigere dette emne" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisk oplåsning er utilgængelig, da Bitwarden-appen er lukket." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authenticating": { "message": "Godkender" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Genereret adgangskode" + }, + "compactMode": { + "message": "Kompakt-tilstand" + }, + "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" + }, + "wide": { + "message": "Bred" + }, + "extraWide": { + "message": "Ekstra bred" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index df664cf8d36..fce84d1b431 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Lizenznummer kopieren" }, + "copyPrivateKey": { + "message": "Privaten Schlüssel kopieren" + }, + "copyPublicKey": { + "message": "Öffentlichen Schlüssel kopieren" + }, + "copyFingerprint": { + "message": "Fingerabdruck kopieren" + }, "copyCustomField": { "message": "$FIELD$ kopieren", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Identität automatisch ausfüllen" }, + "fillVerificationCode": { + "message": "Verifizierungscode ausfüllen" + }, + "fillVerificationCodeAria": { + "message": "Verifizierungscode ausfüllen", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Passwort generieren (kopiert)" }, @@ -438,9 +454,6 @@ "length": { "message": "Länge" }, - "passwordMinLength": { - "message": "Minimale Passwortlänge" - }, "uppercase": { "message": "Großbuchstaben (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mindestanzahl Sonderzeichen" }, - "avoidAmbChar": { - "message": "Mehrdeutige Zeichen vermeiden", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Mehrdeutige Zeichen vermeiden", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Erweiterung bewerten" }, - "rateExtensionDesc": { - "message": "Wir würden uns freuen, wenn du uns mit einer positiven Bewertung helfen könntest!" - }, "browserNotSupportClipboard": { "message": "Den Browser unterstützt das einfache Kopieren nicht. Bitte kopiere es manuell." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Identitäten-Einträge auf der Tab Seite anzeigen, um das Auto-Ausfüllen zu vereinfachen." }, + "clickToAutofillOnVault": { + "message": "Klicke auf Einträge zum automatischen Ausfüllen in der Tresor-Ansicht" + }, "clearClipboard": { "message": "Zwischenablage leeren", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ACHTUNG", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warnung", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Tresor-Export bestätigen" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "In Organisation verschieben" }, - "share": { - "message": "Teilen" - }, "movedItemToOrg": { "message": "$ITEMNAME$ verschoben nach $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Gib den 6-stelligen Verifizierungscode aus deiner Authenticator App ein." }, + "authenticationTimeout": { + "message": "Authentifizierungs-Timeout" + }, + "authenticationSessionTimedOut": { + "message": "Die Authentifizierungssitzung ist abgelaufen. Bitte starte den Anmeldeprozess neu." + }, "enterVerificationCodeEmail": { "message": "Gib den 6-stelligen Bestätigungscode ein, der an $EMAIL$ gesendet wurde.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Wenn eine Anmeldemaske erkannt wird, die Zugangsdaten automatisch ausfüllen während die Webseite lädt." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warnung:$CLOSETAG$ Kompromittierte oder nicht vertrauenswürdige Websites können Auto-Ausfüllen beim Laden einer Seite missbrauchen.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Kompromittierte oder nicht vertrauenswürdige Websites können Auto-Ausfüllen beim Laden der Seite ausnutzen." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identität" }, + "typeSshKey": { + "message": "SSH-Schlüssel" + }, "newItemHeader": { "message": "Neue $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Sichere Notizen" }, + "sshKeys": { + "message": "SSH-Schlüssel" + }, "clear": { "message": "Löschen", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Duplizieren" }, - "passwordGeneratorPolicyInEffect": { - "message": "Eine oder mehrere Organisationsrichtlinien beeinflussen deine Generator-Einstellungen." - }, "passwordGenerator": { "message": "Passwort-Generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Gesperrte Domains" + }, "excludedDomains": { "message": "Ausgeschlossene Domains" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden wird für alle angemeldeten Konten nicht danach fragen Zugangsdaten für diese Domains speichern. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, + "blockedDomainsDesc": { + "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Sie müssen die Seite aktualisieren, damit die Änderungen wirksam werden." + }, + "autofillBlockedNotice": { + "message": "Das automatische Ausfüllen ist für diese Website gesperrt. Dieses Verhalten kann in den Einstellungen überprüft oder geändert werden." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Änderungen gesperrter Domains gespeichert" + }, "excludedDomainsSavedSuccess": { "message": "Änderungen der ausgeschlossenen Domain gespeichert" }, @@ -2373,14 +2394,6 @@ "message": "Send-Details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Sends suchen", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send hinzufügen", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Text standardmäßig ausblenden" }, - "maxAccessCountReached": { - "message": "Maximale Zugriffsanzahl erreicht", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Abgelaufen" }, - "pendingDeletion": { - "message": "Ausstehende Löschung" - }, "passwordProtected": { "message": "Passwortgeschützt" }, @@ -2456,24 +2462,9 @@ "message": "Send bearbeiten", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Welche Art von Send ist das?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Ein eigener Name, um dieses Send zu beschreiben.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Die Datei, die du senden möchtest." - }, "deletionDate": { "message": "Löschdatum" }, - "deletionDateDesc": { - "message": "Das Send wird am angegebenen Datum zur angegebenen Uhrzeit dauerhaft gelöscht.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Das Send wird an diesem Datum dauerhaft gelöscht.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Ablaufdatum" }, - "expirationDateDesc": { - "message": "Falls aktiviert, verfällt der Zugriff auf dieses Send am angegebenen Datum zur angegebenen Uhrzeit.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 Tag" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Benutzerdefiniert" }, - "maximumAccessCount": { - "message": "Maximale Zugriffsanzahl" - }, - "maximumAccessCountDesc": { - "message": "Falls aktiviert, können Benutzer nicht mehr auf dieses Send zugreifen, sobald die maximale Zugriffsanzahl erreicht ist.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optional ein Passwort verlangen, damit Benutzer auf dieses Send zugreifen können.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Füge ein optionales Passwort hinzu, mit dem Empfänger auf dieses Send zugreifen können.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private Notizen zu diesem Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Dieses Send deaktivieren, damit niemand darauf zugreifen kann.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopiere den Send-Link beim Speichern in die Zwischenablage.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Der Text, den du senden möchtest." - }, - "sendHideText": { - "message": "Send-Text standardmäßig ausblenden.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuelle Zugriffsanzahl" - }, "createSend": { "message": "Neues Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Bevor du beginnst" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Um einen Kalender-Datumsauswahl zu verwenden", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "hier klicken", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "zum Öffnen in einem neuen Fenster.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Das angegebene Verfallsdatum ist nicht gültig." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Es gab einen Fehler beim Speichern deiner Lösch- und Verfallsdaten." }, - "hideEmail": { - "message": "Meine E-Mail-Adresse vor den Empfängern ausblenden." - }, "hideYourEmail": { "message": "Verberge deine E-Mail-Adresse vor Betrachtern." }, - "sendOptionsPolicyInEffect": { - "message": "Eine oder mehrere Organisationsrichtlinien beeinflussen deine Send Einstellungen." - }, "passwordPrompt": { "message": "Master-Passwort erneut abfragen" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Fehler" }, - "regenerateUsername": { - "message": "Benutzername neu generieren" + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte den unten aufgelisteten Tresoreintrag bzw. die Tresoreinträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Benutzername generieren" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "E-Mail generieren" }, - "generatorBoundariesHint": { - "message": "Wert muss zwischen $MIN$ und $MAX$ liegen", + "spinboxBoundariesHint": { + "message": "Wert muss zwischen $MIN$ und $MAX$ liegen.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Benutzernamenstyp" + "passwordLengthRecommendationHint": { + "message": " Verwende $RECOMMENDED$ oder mehr Zeichen, um ein starkes Passwort zu generieren.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Verwende $RECOMMENDED$ oder mehr Wörter, um eine starke Passphrase zu generieren.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus-adressierte E-Mail-Adresse", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Websitename" }, - "whatWouldYouLikeToGenerate": { - "message": "Was möchtest du generieren?" - }, - "passwordType": { - "message": "Passworttyp" - }, "service": { "message": "Dienst" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Benachrichtigung erneut senden" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Alle Anmeldeoptionen anzeigen" + }, + "viewAllLoginOptionsV1": { "message": "Alle Anmeldeoptionen anzeigen" }, "notificationSentDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet." }, + "aNotificationWasSentToYourDevice": { + "message": "Eine Benachrichtigung wurde an dein Gerät gesendet" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Stelle sicher, dass dein Konto entsperrt ist und die Fingerabdruck-Phrase mit der vom anderen Gerät übereinstimmt" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Du wirst benachrichtigt, sobald die Anfrage genehmigt wurde" + }, + "needAnotherOptionV1": { + "message": "Brauchst du eine andere Option?" + }, "loginInitiated": { "message": "Anmeldung eingeleitet" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Wird in einem neuen Fenster geöffnet" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Dieses Gerät merken, um zukünftige Anmeldungen reibungslos zu gestalten" + }, "deviceApprovalRequired": { "message": "Geräte-Genehmigung erforderlich. Wähle unten eine Genehmigungsoption aus:" }, + "deviceApprovalRequiredV2": { + "message": "Geräte-Genehmigung erforderlich" + }, + "selectAnApprovalOptionBelow": { + "message": "Wähle unten eine Genehmigungsoption aus" + }, "rememberThisDevice": { "message": "Dieses Gerät merken" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "E-Mail-Adresse des Benutzers fehlt" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktive Benutzer-E-Mail-Adresse nicht gefunden. Du wirst abgemeldet." + }, "deviceTrusted": { "message": "Gerät wird vertraut" }, @@ -3521,6 +3506,14 @@ "message": "Dein Konto entsperren, öffnet sich in einem neuen Fenster", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Zeitbasierter einmaliger Passwort-Verifizierungscode", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Verbleibende Zeit bis zum Ablauf des aktuellen TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Zugangsdaten ausfüllen für", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Zugriff auf" }, + "loggedInExclamation": { + "message": "Angemeldet!" + }, "passkeyNotCopied": { "message": "Passkey wird nicht kopiert" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filter" }, + "filterVault": { + "message": "Tresor filtern" + }, + "filterApplied": { + "message": "Ein Filter angewendet" + }, + "filterAppliedPlural": { + "message": "$COUNT$ Filter angewendet", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Persönliche Details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Anzahl der Vorschläge zum Auto-Ausfüllen von Zugangsdaten auf dem Erweiterungssymbol anzeigen" }, + "showQuickCopyActions": { + "message": "Schnellkopier-Aktionen im Tresor anzeigen" + }, "systemDefault": { "message": "Systemstandard" }, "enterprisePolicyRequirementsApplied": { "message": "Unternehmens-Richtlinienanforderungen wurden auf diese Einstellung angewandt" }, + "sshPrivateKey": { + "message": "Privater Schlüssel" + }, + "sshPublicKey": { + "message": "Öffentlicher Schlüssel" + }, + "sshFingerprint": { + "message": "Fingerabdruck" + }, + "sshKeyAlgorithm": { + "message": "Schlüsseltyp" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Erneut versuchen" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da die Bitwarden Desktop-App geschlossen ist." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." + }, "authenticating": { "message": "Authentifizierung" }, @@ -4656,7 +4721,7 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Gravis", + "message": "Abwärtsakzent", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { @@ -4684,11 +4749,11 @@ "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Und-Zeichen", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Sternzeichen", + "message": "Sternchen", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generiertes Passwort" + }, + "compactMode": { + "message": "Kompaktmodus" + }, + "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" + }, + "wide": { + "message": "Breit" + }, + "extraWide": { + "message": "Extra breit" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index bd2f76249bf..bdb0eb2c17d 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -29,7 +29,7 @@ "message": "Χρήση ενιαίας σύνδεσης" }, "welcomeBack": { - "message": "Καλωσορίσατε και πάλι" + "message": "Καλώς ήρθατε" }, "setAStrongPassword": { "message": "Ορίστε έναν ισχυρό κωδικό πρόσβασης" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Αντιγραφή αριθμού άδειας" }, + "copyPrivateKey": { + "message": "Αντιγραφή ιδιωτικού κλειδιού" + }, + "copyPublicKey": { + "message": "Αντιγραφή δημόσιου κλειδιού" + }, + "copyFingerprint": { + "message": "Αντιγραφή δακτυλικού αποτυπώματος" + }, "copyCustomField": { "message": "Αντιγραφή του «$FIELD$»", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Αυτόματη συμπλήρωση ταυτότητας" }, + "fillVerificationCode": { + "message": "Συμπλήρωση κωδικού επαλήθευσης" + }, + "fillVerificationCodeAria": { + "message": "Συμπλήρωση Κωδικού Επαλήθευσης", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Δημιουργία κωδικού πρόσβασης (αντιγράφηκε)" }, @@ -438,9 +454,6 @@ "length": { "message": "Μήκος" }, - "passwordMinLength": { - "message": "Ελάχιστο μήκος κωδικού πρόσβασης" - }, "uppercase": { "message": "Κεφαλαία (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Ελάχιστοι ειδικοί χαρακτήρες" }, - "avoidAmbChar": { - "message": "Αποφυγή αμφιλεγόμενων χαρακτήρων", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Αποφυγή αμφιλεγόμενων χαρακτήρων", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Βαθμολογήστε την επέκταση" }, - "rateExtensionDesc": { - "message": "Παρακαλούμε σκεφτείτε να μας βοηθήσετε με μια καλή κριτική!" - }, "browserNotSupportClipboard": { "message": "Το πρόγραμμα περιήγησης ιστού δεν υποστηρίζει εύκολη αντιγραφή πρόχειρου. Αντιγράψτε το με το χέρι αντ'αυτού." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Λίστα στοιχείων ταυτότητας στη σελίδα Καρτέλας για εύκολη αυτόματη συμπλήρωση." }, + "clickToAutofillOnVault": { + "message": "Κάντε κλικ στα αντικείμενα για αυτόματη συμπλήρωση στην προβολή Θησαυ/κίου" + }, "clearClipboard": { "message": "Εκκαθάριση Πρόχειρου", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Προειδοποίηση", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Επιβεβαίωση εξαγωγής θησαυ/κίου" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Μετακίνηση σε οργανισμό" }, - "share": { - "message": "Κοινοποίηση" - }, "movedItemToOrg": { "message": "$ITEMNAME$ μετακινήθηκε στο $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Εισάγετε τον 6ψήφιο κωδικό από την εφαρμογή επαλήθευσης." }, + "authenticationTimeout": { + "message": "Χρονικό όριο επαλήθευσης" + }, + "authenticationSessionTimedOut": { + "message": "Λήξη χρονικού ορίου συνεδρίας επαλήθευσης. Παρακαλώ επανεκκινήστε τη διαδικασία σύνδεσης." + }, "enterVerificationCodeEmail": { "message": "Εισάγετε τον 6ψήφιο κωδικό επαλήθευσης τον οποίο λάβατε στο $EMAIL$.", "placeholders": { @@ -1424,7 +1440,7 @@ "message": "URL Διακομιστή" }, "selfHostBaseUrl": { - "message": "URL διακομιστή αυτο-φιλοξενίας", + "message": "Διεύθυνση URL διακομιστή αυτοεξυπηρετητή", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Εάν εντοπιστεί φόρμα σύνδεσης, θα συμπληρωθεί αυτόματα κατά τη φόρτωση της σελίδας." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Προειδοποίηση:$CLOSETAG$ Οι παραβιασμένες ή μη αξιόπιστες ιστοσελίδες μπορούν να εκμεταλλευτούν την αυτόματη συμπλήρωση κατά τη φόρτωση της σελίδας.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Παραβιασμένοι ή μη αξιόπιστοι ιστότοποι μπορούν να εκμεταλλευτούν την αυτόματη συμπλήρωση κατά τη φόρτωση της σελίδας." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Ταυτότητα" }, + "typeSshKey": { + "message": "Κλειδί SSH" + }, "newItemHeader": { "message": "Νέα $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Ασφαλείς σημειώσεις" }, + "sshKeys": { + "message": "Κλειδιά SSH" + }, "clear": { "message": "Εκκαθάριση", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Κλώνος" }, - "passwordGeneratorPolicyInEffect": { - "message": "Μία ή περισσότερες πολιτικές του οργανισμού επηρεάζουν τις ρυθμίσεις της γεννήτριας." - }, "passwordGenerator": { "message": "Γεννήτρια κωδικού πρόσβασης" }, @@ -2318,6 +2324,9 @@ "message": "Τομείς", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Αποκλεισμένοι τομείς" + }, "excludedDomains": { "message": "Εξαιρούμενοι Τομείς" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Το Bitwarden δε θα ρωτήσει για να αποθηκεύσετε τα στοιχεία σύνδεσης για αυτούς τους τομείς, για όλους τους συνδεδεμένους λογαριασμούς. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, + "blockedDomainsDesc": { + "message": "Η αυτόματη συμπλήρωση και άλλες σχετικές λειτουργίες δεν θα προσφερθούν για αυτούς τους ιστότοπους. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Ιστοσελίδα $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Οι αλλαγές αποκλεισμένων τομέων αποθηκεύτηκαν" }, @@ -2373,14 +2394,6 @@ "message": "Στοιχεία Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Αναζήτηση Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Προσθήκη Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Κείμενο" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Απόκρυψη κειμένου από προεπιλογή" }, - "maxAccessCountReached": { - "message": "Φτάσατε στον μέγιστο αριθμό πρόσβασης", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Έληξε" }, - "pendingDeletion": { - "message": "Εκκρεμεί διαγραφή" - }, "passwordProtected": { "message": "Προστατευμένο με κωδικό" }, @@ -2456,24 +2462,9 @@ "message": "Επεξεργασία Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Τι είδους Send είναι αυτό;", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Ένα φιλικό όνομα για την περιγραφή αυτού του Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Το αρχείο που θέλετε να στείλετε." - }, "deletionDate": { "message": "Ημερομηνία διαγραφής" }, - "deletionDateDesc": { - "message": "Το Send θα διαγραφεί οριστικά την καθορισμένη ημερομηνία και ώρα.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Το Send θα διαγραφεί οριστικά σε αυτήν την ημερομηνία.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Ημερομηνία λήξης" }, - "expirationDateDesc": { - "message": "Εάν οριστεί, η πρόσβαση σε αυτό το Send θα λήξει την καθορισμένη ημερομηνία και ώρα.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 ημέρα" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Προσαρμοσμένο" }, - "maximumAccessCount": { - "message": "Μέγιστος Αριθμός Πρόσβασης" - }, - "maximumAccessCountDesc": { - "message": "Εάν οριστεί, οι χρήστες δεν θα μπορούν πλέον να έχουν πρόσβαση σε αυτό το send μόλις επιτευχθεί ο μέγιστος αριθμός πρόσβασης.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Προαιρετικά απαιτείται κωδικός πρόσβασης για τους χρήστες για να έχουν πρόσβαση σε αυτό το Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Προσθέστε έναν προαιρετικό κωδικό πρόσβασης για τους παραλήπτες για πρόσβαση σε αυτό το Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Ιδιωτικές σημειώσεις σχετικά με αυτό το Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Απενεργοποιήστε αυτό το Send έτσι ώστε κανείς να μην μπορεί να έχει πρόσβαση σε αυτό.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Αντιγραφή του συνδέσμου για αυτό το Send στο πρόχειρο κατά την αποθήκευση.", + "message": "Προσθέστε έναν προαιρετικό κωδικό πρόσβασης για τους παραλήπτες για πρόσβαση σε αυτήν την αποστολή.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTextDesc": { - "message": "Το κείμενο που θέλετε να στείλετε." - }, - "sendHideText": { - "message": "Απόκρυψη του κειμένου αυτού του Send από προεπιλογή.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Τρέχων αριθμός πρόσβασης" - }, "createSend": { "message": "Νέο Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Πριν ξεκινήσετε" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Για να χρησιμοποιήσετε έναν επιλογέα ημερομηνίας στυλ ημερολογίου", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "κάντε κλικ εδώ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "για να βγεις από το παράθυρο.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Η ημερομηνία λήξης που δόθηκε δεν είναι έγκυρη." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Παρουσιάστηκε σφάλμα κατά την αποθήκευση των ημερομηνιών διαγραφής και λήξης." }, - "hideEmail": { - "message": "Απόκρυψη της διεύθυνσης email μου από τους παραλήπτες." - }, "hideYourEmail": { "message": "Απόκρυψη της διεύθυνσης email σας από τους θεατές." }, - "sendOptionsPolicyInEffect": { - "message": "Μία ή περισσότερες οργανωτικές πολιτικές επηρεάζουν τις επιλογές send σας." - }, "passwordPrompt": { "message": "Προτροπή νέου κωδικού πρόσβασης" }, @@ -2868,17 +2804,28 @@ "error": { "message": "Σφάλμα" }, - "regenerateUsername": { - "message": "Επαναδημιουργία ονόματος χρήστη" + "decryptionError": { + "message": "Σφάλμα αποκρυπτογράφησης" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Το Bitwarden δεν μπόρεσε να αποκρυπτογραφήσει τα αντικείμενα θησαυ/κίου που αναφέρονται παρακάτω." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "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": "Δημιουργία email" + "message": "Δημιουργία διεύθυνσης ηλ. ταχυδρομείου" }, - "generatorBoundariesHint": { - "message": "Η τιμή πρέπει να είναι μεταξύ $MIN$ και $MAX$", + "spinboxBoundariesHint": { + "message": "Η τιμή πρέπει να είναι μεταξύ $MIN$ και $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Τύπος ονόματος χρήστη" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Συν διεύθυνση ηλ. ταχυδρομείου", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Όνομα ιστοσελίδας" }, - "whatWouldYouLikeToGenerate": { - "message": "Τι θα θέλατε να δημιουργήσετε?" - }, - "passwordType": { - "message": "Τύπος κωδικού πρόσβασης" - }, "service": { "message": "Υπηρεσία" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Επαναποστολή ειδοποίησης" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Δείτε όλες τις επιλογές σύνδεσης" + }, + "viewAllLoginOptionsV1": { "message": "Δείτε όλες τις επιλογές σύνδεσης" }, "notificationSentDevice": { "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, + "aNotificationWasSentToYourDevice": { + "message": "Μια ειδοποίηση στάλθηκε στη συσκευή σας" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Βεβαιωθείτε ότι ο λογαριασμός σας είναι ξεκλείδωτος και ότι η φράση δακτυλικού αποτυπώματος ταιριάζει στην άλλη συσκευή" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Θα ειδοποιηθείτε μόλις εγκριθεί η αίτηση" + }, + "needAnotherOptionV1": { + "message": "Χρειάζεστε μια άλλη επιλογή;" + }, "loginInitiated": { "message": "Η σύνδεση ξεκίνησε" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Ανοίγει σε νέο παράθυρο" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Απομνημόνευση αυτής της συσκευής για την αυτόματες συνδέσεις στο μέλλον" + }, "deviceApprovalRequired": { "message": "Απαιτείται έγκριση συσκευής. Επιλέξτε μια επιλογή έγκρισης παρακάτω:" }, + "deviceApprovalRequiredV2": { + "message": "Απαιτείται έγκριση συσκευής" + }, + "selectAnApprovalOptionBelow": { + "message": "Επιλέξτε μια επιλογή έγκρισης παρακάτω" + }, "rememberThisDevice": { "message": "Απομνημόνευση αυτής της συσκευής" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Η διεύθυνση ηλ. ταχυδρομείου του χρήστη λείπει" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Το ηλ. ταχυδρομείο χρήστη δεν βρέθηκε. Αποσυνδεθήκατε." + }, "deviceTrusted": { "message": "Αξιόπιστη συσκευή" }, @@ -3521,6 +3506,14 @@ "message": "Ξεκλείδωμα του λογαριασμού σας, ανοίγει σε νέο παράθυρο", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Κωδικός Επαλήθευσης Κωδικού Πρόσβασης μιας φοράς", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Χρόνος που απομένει πριν λήξει του τρέχον ΚΕΚΠ", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Συμπλήρωση στοιχείων για", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Πρόσβαση" }, + "loggedInExclamation": { + "message": "Έχετε συνδεθεί!" + }, "passkeyNotCopied": { "message": "Το κλειδί πρόσβασης δε θα αντιγραφεί" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Φίλτρα" }, + "filterVault": { + "message": "Φίλτρο θησαυ/κιου" + }, + "filterApplied": { + "message": "Ένα φίλτρο εφαρμόστηκε" + }, + "filterAppliedPlural": { + "message": "$COUNT$ φίλτρα εφαρμόστηκαν", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Προσωπικές πληροφορίες" }, @@ -4564,13 +4575,13 @@ "message": "Τοποθεσία Αντικειμένου" }, "fileSend": { - "message": "Send αρχείου" + "message": "Δημιουργία Αποστολής" }, "fileSends": { "message": "Send αρχείων" }, "textSend": { - "message": "Send κειμένου" + "message": "Κείμενο Αποστολής" }, "textSends": { "message": "Send κειμένων" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Εμφάνιση αριθμού προτάσεων αυτόματης συμπλήρωσης σύνδεσης στο εικονίδιο επέκτασης" }, + "showQuickCopyActions": { + "message": "Εμφάνιση ενεργειών γρήγορης αντιγραφής στο Θησαυ/κιο" + }, "systemDefault": { "message": "Προεπιλογή συστήματος" }, "enterprisePolicyRequirementsApplied": { "message": "Οι απαιτήσεις της πολιτικής για επιχειρήσεις έχουν εφαρμοστεί σε αυτήν τη ρύθμιση" }, + "sshPrivateKey": { + "message": "Ιδιωτικό κλειδί" + }, + "sshPublicKey": { + "message": "Δημόσιο κλειδί" + }, + "sshFingerprint": { + "message": "Δακτυλικό αποτύπωμα" + }, + "sshKeyAlgorithm": { + "message": "Τύπος κλειδιού" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Επανάληψη" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Δεν έχετε δικαίωμα να επεξεργαστείτε αυτό το αντικείμενο" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή απαιτείται πρώτα το ξεκλείδωμα με PIN ή κωδικό πρόσβασης." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο προς το παρόν." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή η εφαρμογή Bitwarden επιφάνειας εργασίας είναι κλειστή." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Ταυτοποίηση" }, @@ -4656,7 +4721,7 @@ "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": { @@ -4680,15 +4745,15 @@ "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": "Αστερίσκος", + "message": "Πολλαπλασιασμός", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { @@ -4732,11 +4797,11 @@ "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": { @@ -4744,15 +4809,15 @@ "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": { @@ -4776,7 +4841,7 @@ "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": { @@ -4786,6 +4851,57 @@ "message": "Κεφαλαία" }, "generatedPassword": { - "message": "Generated password" + "message": "Τυχαίος κωδικός πρόσβασης" + }, + "compactMode": { + "message": "Συμπαγής μορφή" + }, + "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": "Πλάτος εφαρμογής" + }, + "wide": { + "message": "Φαρδύ" + }, + "extraWide": { + "message": "Εξαιρετικά φαρδύ" } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a62dac05430..51fb3a0a770 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -192,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -447,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -521,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -641,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -1007,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1125,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1149,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1315,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1503,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1851,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2043,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2330,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2339,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "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." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2357,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2385,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2409,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2468,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2493,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2512,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2631,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2658,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2880,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2889,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2903,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2928,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3150,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3249,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3325,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3533,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3761,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4023,8 +4007,8 @@ "passkeyRemoved": { "message": "Passkey removed" }, - "autofillSuggestions": { - "message": "Autofill suggestions" + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" @@ -4252,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4599,6 +4598,9 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, @@ -4668,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4823,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 87eee8c90c0..7e938bb6b2c 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Auto-fill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy auto-fill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organisation" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, auto-fill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit auto-fill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organisation policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organisation policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4768,7 +4833,7 @@ "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Full stop", + "message": "Period", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index c5b294aa3a2..70e725c3a88 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy licence number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Auto-fill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy auto-fill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm Vault Export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organisation" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, automatically perform an auto-fill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit auto-fill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organisation policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded Domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion Date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration Date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Disable this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current Access Count" - }, "createSend": { "message": "Create New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organisation policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate Username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate Username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username Type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus Addressed Email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website Name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password Type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 42e56a56ba4..d9cd2517816 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -23,10 +23,10 @@ "message": "¿Nuevo en Bitwarden?" }, "logInWithPasskey": { - "message": "Iniciar sesión con clave de acceso" + "message": "Log in with passkey" }, "useSingleSignOn": { - "message": "Usar inicio de sesión único" + "message": "Use single sign-on" }, "welcomeBack": { "message": "Bienvenido de nuevo" @@ -120,7 +120,7 @@ "message": "Copiar contraseña" }, "copyPassphrase": { - "message": "Copiar frase de contraseña" + "message": "Copy passphrase" }, "copyNote": { "message": "Copiar nota" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copiar número de licencia" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copiar $FIELD$", "placeholders": { @@ -168,7 +177,7 @@ "message": "Copiar notas" }, "fill": { - "message": "Rellenar", + "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." }, "autoFill": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autocompletar identidad" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generar contraseña (copiada)" }, @@ -427,7 +443,7 @@ "message": "Generar contraseña" }, "generatePassphrase": { - "message": "Generar frase de contraseña" + "message": "Generate passphrase" }, "regeneratePassword": { "message": "Regenerar contraseña" @@ -438,9 +454,6 @@ "length": { "message": "Longitud" }, - "passwordMinLength": { - "message": "Longitud mínima de contraseña" - }, "uppercase": { "message": "Mayúsculas (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mínimo de caracteres especiales" }, - "avoidAmbChar": { - "message": "Evitar caracteres ambiguos", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Evitar caracteres ambiguos", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Valora la extensión" }, - "rateExtensionDesc": { - "message": "¡Por favor, considera ayudarnos con una buena reseña!" - }, "browserNotSupportClipboard": { "message": "Tu navegador web no soporta copiar al portapapeles facilmente. Cópialo manualmente." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Listar los elementos de identidad en la página para facilitar el auto-rellenado." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Vaciar portapapeles", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ADVERTENCIA", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Advertencia", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirma la exportación de la caja fuerte" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Mover a la Organización" }, - "share": { - "message": "Compartir" - }, "movedItemToOrg": { "message": "$ITEMNAME$ se desplazó a $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Introduce el código de verificación de 6 dígitos de tu aplicación autenticadora." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Introduce el código de verificación de 6 dígitos que te ha sido enviado por correo electrónico", "placeholders": { @@ -1459,7 +1475,7 @@ "message": "Display identities as suggestions" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Mostrar tarjetas como sugerencias" }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" @@ -1494,24 +1510,11 @@ "enableAutoFillOnPageLoadDesc": { "message": "Si se detecta un formulario, realizar automáticamente un autorellenado cuando la web cargue." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Los sitios web vulnerados o no confiables pueden explotar el autorelleno al cargar la página." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Más información sobre riesgos" }, "learnMoreAboutAutofill": { "message": "Más información sobre el relleno automático" @@ -1580,7 +1583,7 @@ "message": "Booleano" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Casilla de verificación" }, "cfTypeLinked": { "message": "Vinculado", @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identidad" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "Nuevo $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Notas seguras" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Limpiar", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clonar" }, - "passwordGeneratorPolicyInEffect": { - "message": "Una o más políticas de la organización están afectando la configuración del generador" - }, "passwordGenerator": { "message": "Generador de contraseñas" }, @@ -2318,6 +2324,9 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominios excluidos" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no pedirá que se guarden los datos de acceso para estos dominios en todas las sesiones iniciadas. Debe actualizar la página para que los cambios surtan efecto." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,19 +2394,11 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Buscar Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Añadir Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Texto" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Texto a compartir" }, "sendTypeFile": { "message": "Archivo" @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Número máximo de accesos alcanzado", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Caducado" }, - "pendingDeletion": { - "message": "Borrado pendiente" - }, "passwordProtected": { "message": "Protegido por contraseña" }, @@ -2456,24 +2462,9 @@ "message": "Editar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "¿Qué tipo de Send es este?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nombre amigable para describir este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "El archivo que desea enviar." - }, "deletionDate": { "message": "Fecha de eliminación" }, - "deletionDateDesc": { - "message": "El Send se eliminará permanentemente en la fecha y hora especificadas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Fecha de caducidad" }, - "expirationDateDesc": { - "message": "Si se establece, el acceso a este Send caducará en la fecha y hora especificadas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 día" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalizado" }, - "maximumAccessCount": { - "message": "Número máximo de accesos" - }, - "maximumAccessCountDesc": { - "message": "Si se establece, los usuarios ya no podrán acceder a este Send una vez que se alcance el número máximo de accesos.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Opcionalmente se requiere una contraseña para que los usuarios accedan a este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Notas privadas sobre este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Desactiva este Send para que nadie pueda acceder a él.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copiar el enlace del Send en el portapapeles al guardar.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "El texto que quieres enviar." - }, - "sendHideText": { - "message": "Ocultar el texto de este Envío por defecto.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Número de acceso actual" - }, "createSend": { "message": "Crear Envío nuevo", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Antes de empezar" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Para usar un selector de fechas de estilo calendario", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "haz click aquí", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "para abrir la ventana.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "La fecha de caducidad proporcionada no es válida." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Hubo un error al guardar las fechas de eliminación y caducidad." }, - "hideEmail": { - "message": "Ocultar mi dirección de correo electrónico a los destinatarios." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Una o más políticas de organización están afectando sus opciones del Send." - }, "passwordPrompt": { "message": "Volver a preguntar contraseña maestra" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerar nombre de usuario" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generar nombre de usuario" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tipo de nombre de usuario" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Dirección con sufijo", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nombre del sitio web" }, - "whatWouldYouLikeToGenerate": { - "message": "¿Qué te gustaría generar?" - }, - "passwordType": { - "message": "Tipo de contraseña" - }, "service": { "message": "Servicio" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Reenviar notificación" }, - "viewAllLoginOptions": { - "message": "Ver todas las opciones de acceso" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Inicio de sesión en proceso" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Abre en una nueva ventana" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Se requiere aprobación del dispositivo. Seleccione una opción de aprobación a continuación:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Recordar este dispositivo" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Falta el correo electrónico del usuario" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositivo de confianza" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Rellenar credenciales para", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "La clave de acceso no se copiará" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtros" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Datos personales" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "No tiene permiso de editar este elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4708,11 +4773,11 @@ "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Más", + "message": "Plus", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Igual", + "message": "Equals", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { @@ -4736,15 +4801,15 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Contrabarra", + "message": "Back slash", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Dos puntos", + "message": "Colon", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Punto y coma", + "message": "Semicolon", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 81538f3bb22..5245de4fb7e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -20,16 +20,16 @@ "message": "Konto loomine" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Esimene kord?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logi sisse pääsuvõtmega" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Tere tulemast tagasi" }, "setAStrongPassword": { "message": "Määra tugev parool" @@ -84,7 +84,7 @@ "message": "Liitu organisatsiooniga" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Liitu $ORGANIZATIONNAME$ organisatsiooniga", "placeholders": { "organizationName": { "content": "$1", @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopeeri litsentsi number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Täida identiteet" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Genereeri parool (kopeeritakse)" }, @@ -438,9 +454,6 @@ "length": { "message": "Pikkus" }, - "passwordMinLength": { - "message": "Lühim lubatud parooli pikkus" - }, "uppercase": { "message": "Suurtäht (A-Z) ", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Vähim arv spetsiaalmärke" }, - "avoidAmbChar": { - "message": "Väldi ebamääraseid kirjamärke", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Väldi raskesti eristatavaid tähti ja sümboleid", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Hinda seda laiendust" }, - "rateExtensionDesc": { - "message": "Soovi korral võid meid positiivse hinnanguga toetada!" - }, "browserNotSupportClipboard": { "message": "Kasutatav brauser ei toeta lihtsat lõikelaua kopeerimist. Kopeeri see käsitsi." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Kuvab \"Kaart\" vaates identiteete, et neid saaks kiiresti sisestada" }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Lõikelaua sisu kustutamine", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "HOIATUS", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Hoidla eksportimise kinnitamine" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Teisalda organisatsiooni" }, - "share": { - "message": "Jaga" - }, "movedItemToOrg": { "message": "$ITEMNAME$ teisaldati $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Sisesta autentimise rakendusest 6 kohaline number." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Sisesta 6 kohaline number, mis saadeti e-posti aadressile $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Sisselogimise vormi tuvastamisel sisestatakse sinna kontoandmed automaatselt." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Häkitud või ebausaldusväärsed veebilehed võivad lehe laadimisel automaatset sisestamist kuritarvitada." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identiteet" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "Uus $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Turvalised märkmed" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Tühjenda", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Kloon" }, - "passwordGeneratorPolicyInEffect": { - "message": "Organisatsiooni seaded mõjutavad parooli genereerija sätteid." - }, "passwordGenerator": { "message": "Parooli genereerija" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Väljajäetud domeenid" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Otsi Sende", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Lisa Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Maksimaalne ligipääsude arv on saavutatud", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Aegunud" }, - "pendingDeletion": { - "message": "Kustutamise ootel" - }, "passwordProtected": { "message": "Parooliga kaitstud" }, @@ -2456,24 +2462,9 @@ "message": "Muuda Sendi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Mis tüüpi Send see on?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Sisesta Sendi nimi (kohustuslik).", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Fail, mida soovid saata." - }, "deletionDate": { "message": "Kustutamise kuupäev" }, - "deletionDateDesc": { - "message": "Send kustutatakse määratud kuupäeval ja kellaajal jäädavalt.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Aegumiskuupäev" }, - "expirationDateDesc": { - "message": "Selle valimisel ei pääse sellele Sendile enam pärast määratud kuupäeva ligi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 päev" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Kohandatud" }, - "maximumAccessCount": { - "message": "Maksimaalne ligipääsude arv" - }, - "maximumAccessCountDesc": { - "message": "Selle valimisel ei saa kasutajad pärast maksimaalse ligipääsude arvu saavutamist sellele Sendile enam ligi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Soovi korral nõua parooli, millega Sendile ligi pääseb.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Privaatne märkus selle Sendi kohta.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Keela see Send, et keegi ei pääseks sellele ligi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopeeri Sendi salvestamisel link lõikelauale.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Tekst, mida soovid saata." - }, - "sendHideText": { - "message": "Vaikeolekus peida selle Sendi tekst.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Hetkeline ligipääsude arv" - }, "createSend": { "message": "Loo uus Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Enne alustamist" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Kalendri stiilis kuupäeva valimiseks", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "kliki siia,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "et avada Bitwarden uues aknas.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Valitud aegumiskuupäev ei ole õige." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Kustutamis- ja aegumiskuupäevade salvestamisel ilmnes tõrge." }, - "hideEmail": { - "message": "Ära näita saajatele minu e-posti aadressi." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Organisatsiooni seaded mõjutavad sinu Sendi sätteid." - }, "passwordPrompt": { "message": "Nõutav on ülemparool" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Viga" }, - "regenerateUsername": { - "message": "Genereeri kasutajanimi uuesti" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Genereeri kasutajanimi" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Kasutajanime tüüp" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plussiga e-posti aadress", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Veebilehe nimi" }, - "whatWouldYouLikeToGenerate": { - "message": "Mida sa soovid genereerida?" - }, - "passwordType": { - "message": "Parooli tüüp" - }, "service": { "message": "Teenus" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Saada märguanne uuesti" }, - "viewAllLoginOptions": { - "message": "Vaata kõiki valikuid" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Sisselogimine on käivitatud" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Avaneb uues aknas" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Nõutav on seadme kinnitamine. Vali kinnitamise meetod alt:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Mäleta seda seadet" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Kasutaja e-post on puudulik" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Seade on usaldusväärne" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Pääsukoodi ei kopeerita" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 3d0ac87989a..fc875be6da0 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Auto-bete nortasuna" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Sortu pasahitza (kopiatuta)" }, @@ -438,9 +454,6 @@ "length": { "message": "Luzera" }, - "passwordMinLength": { - "message": "Pasahitzaren gutxieneko luzera" - }, "uppercase": { "message": "Letra larria (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Gutxieneko karaktere bereziak" }, - "avoidAmbChar": { - "message": "Saihestu karaktere anbiguoak", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Baloratu gehigarria" }, - "rateExtensionDesc": { - "message": "Mesedez, aipu on batekin lagundu!" - }, "browserNotSupportClipboard": { "message": "Zure web nabigatzaileak ez du onartzen arbelean erraz kopiatzea. Eskuz kopiatu." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Erakutsi identitateak fitxa orrian, erraz auto-betetzeko." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Hustu arbela", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "KONTUZ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Baieztatu kutxa gotorra esportatzea" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Mugitu erakundera" }, - "share": { - "message": "Partekatu" - }, "movedItemToOrg": { "message": "$ITEMNAME$ $ORGNAME$-ra mugituta", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Sartu zure autentifikazio aplikazioaren 6 digituko egiaztatze kodea." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Sartu $EMAIL$-era bidalitako 6 digituko egiaztatze-kodea.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Saio-hasierako formulario bat detektatzen bada, auto-bete webgunea kargatzen denean." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitatea" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Ohar seguruak" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Ezabatu", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Klonatu" }, - "passwordGeneratorPolicyInEffect": { - "message": "Erakundeko politika batek edo gehiagok sortzailearen konfigurazioari eragiten diote." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Kanporatutako domeinuak" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Send-ak bilatu", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Gehitu Send-a", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Testua" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Sarbide kopuru maximoa gaindituta", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Iraungita" }, - "pendingDeletion": { - "message": "Ezabatzea egiteke" - }, "passwordProtected": { "message": "Pasahitz babestua" }, @@ -2456,24 +2462,9 @@ "message": "Editatu Send-a", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Zein Send mota da hau?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Send hau deskribatzeko izena.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Bidali nahi duzun fitxategia." - }, "deletionDate": { "message": "Ezabatze data" }, - "deletionDateDesc": { - "message": "Send-a betiko ezabatuko da zehaztutako datan eta orduan.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Iraungitze data" }, - "expirationDateDesc": { - "message": "Hala ezartzen bada, Send honetarako sarbidea zehaztutako egunean eta orduan amaituko da.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "Egun 1" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Pertsonalizatua" }, - "maximumAccessCount": { - "message": "Sarbide kopuru maximoa" - }, - "maximumAccessCountDesc": { - "message": "Hala ezartzen bada, erabiltzaileak ezin izango dira Send honetara sartu gehienezko sarbide kopurura iritsi ondoren.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Nahi izanez gero, pasahitza eskatu erabiltzaileak Send honetara sar daitezen.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Send honi buruzko ohar pribatuak.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Desgaitu Send hau inor sar ez dadin.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Gordetzean, kopiatu Send honen esteka arbelean.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Bidali nahi duzun testua." - }, - "sendHideText": { - "message": "Ezkutatu Send-eko testu hau, modu lehenetsian.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Uneko sarbide kopurua" - }, "createSend": { "message": "Sortu Send berria", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Hasi aurretik" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Data aukeratzeko egutegi modua erabiltzeko", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klikatu hemen", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "leihoa irekitzeko.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Iraungitze data ez da baliozkoa." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Akatsa gertatu da ezabatze eta iraungitze datak gordetzean." }, - "hideEmail": { - "message": "Ezkutatu nire emaila hartzaileei." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Erakundeko politika batek edo gehiagok Send-eko aukerei eragiten diote." - }, "passwordPrompt": { "message": "Berriro eskatu pasahitz nagusia" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Akatsa" }, - "regenerateUsername": { - "message": "Berrezarri erabiltzaile izena" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Sortu erabiltzaile izena" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Erabiltzaile izen mota" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Atzizkidun emaila", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Webgune izena" }, - "whatWouldYouLikeToGenerate": { - "message": "Zer sortu nahi duzu?" - }, - "passwordType": { - "message": "Pasahitz mota" - }, "service": { "message": "Zerbitzua" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Berbidali jakinarazpena" }, - "viewAllLoginOptions": { - "message": "Ikusi erregistro guztiak ezarpenetan" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Leiho berri batean irekitzen da" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Gogoratu gailu hau" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Erabiltzailearen emaila falta da" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 15aa92a669d..d2362a2655f 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "پر کردن خودکار هویت" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "ساخت کلمه عبور (کپی شد)" }, @@ -438,9 +454,6 @@ "length": { "message": "طول" }, - "passwordMinLength": { - "message": "حداقل طول گذرواژه" - }, "uppercase": { "message": "حروف بزرگ (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "حداقل حرف خاص" }, - "avoidAmbChar": { - "message": "از کاراکترهای مبهم اجتناب کن", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "به این افزونه امتیاز دهید" }, - "rateExtensionDesc": { - "message": "لطفاً با یک بررسی خوب به ما کمک کنید!" - }, "browserNotSupportClipboard": { "message": "مرورگر شما از کپی کلیپ بورد آسان پشتیبانی نمی‌کند. به جای آن به صورت دستی کپی کنید." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "موارد هویتی را در صفحه برگه برای پر کردن خودکار آسان فهرست کن." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "پاکسازی کلیپ بورد", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "اخطار", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "برون ریزی گاوصندوق را تأیید کنید" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "انتقال به سازمان" }, - "share": { - "message": "اشتراک گذاری" - }, "movedItemToOrg": { "message": "$ITEMNAME$ منتقل شد به $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "کد ۶ رقمی تأیید را از برنامه احراز هویت وارد کنید." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "کد ۶ رقمی تأیید را که به $EMAIL$ ایمیل شده را وارد کنید.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "اگر یک فرم ورودی شناسایی شد، وقتی صفحه وب بارگذاری شد، به صورت خودکار پر شود." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "وب‌سایت‌های در معرض خطر یا نامعتبر می‌توانند از پر کردن خودکار در بارگذاری صفحه سوء استفاده کنند." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "هویت" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "$TYPE$ جدید", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "یادداشت‌های امن" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "پاک کردن", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "شبیه سازی" }, - "passwordGeneratorPolicyInEffect": { - "message": "یک یا چند سیاست سازمان بر تنظیمات تولید کننده شما تأثیر می‌گذارد." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "دامنه های مستثنی" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "جستجوی ارسال‌ها", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "افزودن ارسال", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "متن" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "به حداکثر تعداد دسترسی رسیده است", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "منقضی شده" }, - "pendingDeletion": { - "message": "در انتظار حذف" - }, "passwordProtected": { "message": "محافظت ‌شده با کلمه عبور" }, @@ -2456,24 +2462,9 @@ "message": "ویرایش ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "این چه نوع ارسالی است؟", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "یک نام دوستانه برای توصیف این ارسال.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "پرونده ای که می‌خواهید ارسال کنید." - }, "deletionDate": { "message": "تاریخ حذف" }, - "deletionDateDesc": { - "message": "ارسال در تاریخ و ساعت مشخص شده برای همیشه حذف خواهد شد.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "تاريخ انقضاء" }, - "expirationDateDesc": { - "message": "در صورت تنظیم، دسترسی به این ارسال در تاریخ و ساعت مشخص شده منقضی می‌شود.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "۱ روز" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "سفارشی" }, - "maximumAccessCount": { - "message": "تعداد دسترسی حداکثر" - }, - "maximumAccessCountDesc": { - "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی‌توانند به این ارسال دسترسی پیدا کنند.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "به صورت اختیاری برای دسترسی کاربران به این ارسال به یک کلمه عبور نیاز دارید.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "یادداشت های خصوصی در مورد این ارسال.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "این ارسال را غیرفعال کنید تا کسی نتواند به آن دسترسی پیدا کند.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "پس از ذخیره، این پیوند ارسال را به کلیپ بورد کپی کن.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "متنی که می‌خواهید ارسال کنید." - }, - "sendHideText": { - "message": "متن این ارسال را به طور پیش فرض پنهان کن.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "تعداد دسترسی فعلی" - }, "createSend": { "message": "ارسال جدید", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "قبل از اینکه شروع کنی" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "برای استفاده از انتخابگر تاریخ به سبک تقویم", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "اینجا کلیک کنید", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "برای خروج از پنجره خود.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "تاریخ انقضاء ارائه شده معتبر نیست." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, - "hideEmail": { - "message": "نشانی ایمیلم را از گیرندگان مخفی کن." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "یک یا چند سیاست سازمان بر گزینه های ارسال شما تأثیر می‌گذارد." - }, "passwordPrompt": { "message": "درخواست مجدد کلمه عبور اصلی" }, @@ -2868,8 +2804,19 @@ "error": { "message": "خطا" }, - "regenerateUsername": { - "message": "ایجاد مجدد نام کاربری" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "ایجاد نام کاربری" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "نوع نام کاربری" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "به علاوه نشانی ایمیل داده شده", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "نام وب‌سایت" }, - "whatWouldYouLikeToGenerate": { - "message": "چه چیزی دوست دارید تولید کنید؟" - }, - "passwordType": { - "message": "نوع کلمه عبور" - }, "service": { "message": "سرویس" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "ارسال مجدد اعلان" }, - "viewAllLoginOptions": { - "message": "مشاهده همه گزینه‌های ورود به سیستم" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "در پنجره جدید باز می‌شود" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "ایمیل کاربر وجود ندارد" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "کلید عبور کپی نمی‌شود" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 054362a4a26..0dd96ef9a02 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopioi ajokortin numero" }, + "copyPrivateKey": { + "message": "Kopioi yksityinen avain" + }, + "copyPublicKey": { + "message": "Kopioi julkinen avain" + }, + "copyFingerprint": { + "message": "Kopioi sormenjälki" + }, "copyCustomField": { "message": "Kopioi $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Automaattitäytä henkilöllisyys" }, + "fillVerificationCode": { + "message": "Täytä vahvistuskoodi" + }, + "fillVerificationCodeAria": { + "message": "Täytä vahvistuskoodi", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Luo salasana (leikepöydälle)" }, @@ -438,9 +454,6 @@ "length": { "message": "Pituus" }, - "passwordMinLength": { - "message": "Salasanan vähimmäispituus" - }, "uppercase": { "message": "Isot kirjaimet (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Erikoismerkkejä vähintään" }, - "avoidAmbChar": { - "message": "Vältä epäselviä merkkejä", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Vältä epäselviä merkkejä", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Arvioi laajennus" }, - "rateExtensionDesc": { - "message": "Harkitse tukemistamme hyvällä arvostelulla!" - }, "browserNotSupportClipboard": { "message": "Selaimesi ei tue helppoa leikepöydälle kopiointia. Kopioi kohde manuaalisesti." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Näytä henkilöllisyydet Välilehti-sivulla automaattitäytön helpottamiseksi." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Tyhjennä leikepöytä", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "VAROITUS", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Varoitus", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Vahvista holvin vienti" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Siirrä organisaatiolle" }, - "share": { - "message": "Jaa" - }, "movedItemToOrg": { "message": "$ITEMNAME$ siirrettiin organisaatioon $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Syötä todennussovelluksesi näyttämä kuusinumeroinen todennuskoodi." }, + "authenticationTimeout": { + "message": "Todennuksen aikakatkaisu" + }, + "authenticationSessionTimedOut": { + "message": "Todennusistunto aikakatkaistiin. Ole hyvä ja aloita kirjautumisprosessi uudelleen." + }, "enterVerificationCodeEmail": { "message": "Syötä osoitteeseen $EMAIL$ lähetetty kuusinumeroinen todennuskoodi.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Automaattinen täyttö suoritetaan sivun avautuessa, jos sivulla havaitaan kirjautumislomake." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Varoitus:$CLOSETAG$ Vaarantuneet tai epäluotettavat sivustot voivat hyväksikäyttää sivun avautuessa suoritettavaa automaattista täyttöä.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Vaarantuneet tai epäluotettavat sivustot voivat hyväksikäyttää sivun avautuessa suoritettavaa automaattista täyttöä." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Henkilöllisyys" }, + "typeSshKey": { + "message": "SSH-avain" + }, "newItemHeader": { "message": "Uusi $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Salatut muistiot" }, + "sshKeys": { + "message": "SSH-avaimet" + }, "clear": { "message": "Tyhjennä", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Kloonaa" }, - "passwordGeneratorPolicyInEffect": { - "message": "Yksi tai useampi organisaatiokäytäntö vaikuttaa generaattorisi asetuksiin." - }, "passwordGenerator": { "message": "Salasanageneraattori" }, @@ -2099,7 +2105,7 @@ "message": "Aikakatkaisutoiminnon vahvistus" }, "autoFillAndSave": { - "message": "Täytä automaattisesti ja tallenna" + "message": "Automaattitäytä ja tallenna" }, "fillAndSave": { "message": "Täytä ja tallenna" @@ -2318,6 +2324,9 @@ "message": "Verkkotunnukset", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Ohitettavat verkkotunnukset" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ei pyydä kirjautumistietojen tallennusta näillä verkkotunnuksilla. Koskee kaikkia kirjautuneita tilejä. Ota muutokset käyttöön päivittämällä sivu." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Verkkotunnus $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Rajoitettujen verkkotunnusten muutokset tallennettiin" }, @@ -2373,14 +2394,6 @@ "message": "Sendin tiedot", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Etsi Sendeistä", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Lisää Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Teksti" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Piilota teksti oletuksena" }, - "maxAccessCountReached": { - "message": "Käyttökertojen enimmäismäärä saavutettu", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Erääntynyt" }, - "pendingDeletion": { - "message": "Odottaa poistoa" - }, "passwordProtected": { "message": "Salasanasuojattu" }, @@ -2456,24 +2462,9 @@ "message": "Muokkaa Sendiä", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Minkä tyyppinen Send tämä on?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Kuvaava nimi Sendille.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Tiedosto, jonka haluat lähettää." - }, "deletionDate": { "message": "Poistoajankohta" }, - "deletionDateDesc": { - "message": "Send poistuu pysyvästi määritettynä ajankohtana.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send poistuu pysyvästi tänä päivänä.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Erääntymisajankohta" }, - "expirationDateDesc": { - "message": "Jos määritetty, Send erääntyy määritettynä ajankohtana.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 päivä" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Mukautettu" }, - "maximumAccessCount": { - "message": "Käyttöoikeuksien enimmäismäärä" - }, - "maximumAccessCountDesc": { - "message": "Jos määritetty, käyttäjät eivät voi avata Sendiä käyttökertojen enimmäismäärän täytyttyä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Halutessasi, vaadi käyttäjiä syöttämään salasana Sendin avaamiseksi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Lisää valinnainen salasana vastaanottajille tähän Sendiin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Yksityisiä merkintöjä tästä Sendistä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Poista Send käytöstä, jottei kukaan voi avata sitä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopioi Sendin linkki leikepöydälle tallennettaessa.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teksti, jonka haluat lähettää." - }, - "sendHideText": { - "message": "Piilota Sendin teksti oletuksena.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Käyttökertojen nykyinen määrä" - }, "createSend": { "message": "Uusi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Ennen kuin aloitat" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Käyttääksesi kalenterityylistä päivämäärän valintaa", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klikkaa tästä", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "erillistä valintaa varten.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Määritetty erääntymismisajankohta on virheellinen." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Tapahtui virhe tallennettaessa poisto- ja erääntymisajankohtia." }, - "hideEmail": { - "message": "Piilota sähköpostiosoitteeni vastaanottajilta." - }, "hideYourEmail": { "message": "Piilota sähköpostiosoitteeni avaajilta." }, - "sendOptionsPolicyInEffect": { - "message": "Yksi tai useampi organisaatiokäytäntö vaikuttaa Send-asetuksiisi." - }, "passwordPrompt": { "message": "Pääsalasanan uudelleenkysely" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Virhe" }, - "regenerateUsername": { - "message": "Luo uusi käyttäjätunnus" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Luo käyttäjätunnus" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Luo sähköpostiosoite" }, - "generatorBoundariesHint": { - "message": "Arvon tulee olla väliltä $MIN$—$MAX$", + "spinboxBoundariesHint": { + "message": "Arvon tulee olla väliltä $MIN$—$MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Käyttäjätunnuksen tyyppi" + "passwordLengthRecommendationHint": { + "message": " Käytä $RECOMMENDED$ tai useampaa merkkiä vahvan salasanan luomiseen.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Käytä $RECOMMENDED$ tai useampaa sanaa vahvan salalauseen luomiseen.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus+merkkinen sähköposti", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Verkkosivuston nimi" }, - "whatWouldYouLikeToGenerate": { - "message": "Mitä haluat luoda?" - }, - "passwordType": { - "message": "Salasanan tyyppi" - }, "service": { "message": "Palvelu" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Lähetä ilmoitus uudelleen" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Näytä kaikki kirjautumisvaihtoehdot" + }, + "viewAllLoginOptionsV1": { "message": "Näytä kaikki kirjautumisvaihtoehdot" }, "notificationSentDevice": { "message": "Laitteellesi on lähetetty ilmoitus." }, + "aNotificationWasSentToYourDevice": { + "message": "Laitteeseesi lähetettiin ilmoitus" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Sinulle ilmoitetaan, kun pyyntö on hyväksytty" + }, + "needAnotherOptionV1": { + "message": "Tarvitsetko toisen vaihtoehdon?" + }, "loginInitiated": { "message": "Kirjautuminen aloitettu" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Avautuu uudessa ikkunassa" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Muista tämä laite tehdäksesi tulevista kirjautumisista saumattomia" + }, "deviceApprovalRequired": { "message": "Laitehyväksyntä vaaditaan. Valitse hyväksyntätapa alta:" }, + "deviceApprovalRequiredV2": { + "message": "Laitteen hyväksyntä vaaditaan" + }, + "selectAnApprovalOptionBelow": { + "message": "Valitse hyväksyntävaihtoehto alla" + }, "rememberThisDevice": { "message": "Muista tämä laite" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Käyttäjän sähköpostiosoite puuttuu" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiivista käyttäjän sähköpostiosoitetta ei löytynyt. Kirjaudutaan ulos." + }, "deviceTrusted": { "message": "Laitteeseen luotettu" }, @@ -3521,6 +3506,14 @@ "message": "Avaa tilisi lukitus. Avautuu uudessa ikkunassa.", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Aikaperusteinen kertakäyttöinen salasanan vahvistuskoodi", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Aika jäljellä, ennen kuin nykyinen TOTP vanhenee", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Täytä kirjautumistiedot kohteesta", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Avataan" }, + "loggedInExclamation": { + "message": "Kirjautuminen onnistui!" + }, "passkeyNotCopied": { "message": "Pääsyavainta ei kopioida" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Suodattimet" }, + "filterVault": { + "message": "Suodata holvi" + }, + "filterApplied": { + "message": "Yksi suodatin käytössä" + }, + "filterAppliedPlural": { + "message": "$COUNT$ suodatinta käytössä", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Henkilökohtaiset tiedot" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Näytä automaattitäytön ehdotusten määrä laajennuksen kuvakkeessa" }, + "showQuickCopyActions": { + "message": "Näytä pikakopiointitoiminnot holvissa" + }, "systemDefault": { "message": "Järjestelmän oletus" }, "enterprisePolicyRequirementsApplied": { "message": "Yrityskäytännön vaatimukset vaikuttavat tähän asetukseen" }, + "sshPrivateKey": { + "message": "Yksityinen avain" + }, + "sshPublicKey": { + "message": "Julkinen avain" + }, + "sshFingerprint": { + "message": "Sormenjälki" + }, + "sshKeyAlgorithm": { + "message": "Avaintyyppi" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Yritä uudelleen" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Sinulla ei ole oikeutta muokata tätä kohdetta" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Todennetaan" }, @@ -4680,7 +4745,7 @@ "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Hattu", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4708,7 +4773,7 @@ "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Plus-merkki", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Luotu salasana" + }, + "compactMode": { + "message": "Pienikokoinen tila" + }, + "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": "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": "Muistuta myöhemmin" + }, + "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": "Ota kaksivaiheinen kirjautuminen käyttöön" + }, + "changeAcctEmail": { + "message": "Muuta tilin sähköpostiosoitetta" + }, + "extensionWidth": { + "message": "Laajennuksen leveys" + }, + "wide": { + "message": "Leveä" + }, + "extraWide": { + "message": "Erittäin leveä" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index f779ead0dae..d0fdf1018fb 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Awtomatikong punan ang pagkakakilanlan" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Maglagay ng Password" }, @@ -438,9 +454,6 @@ "length": { "message": "Kahabaan" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Inakamababang espesyal" }, - "avoidAmbChar": { - "message": "Iwasan ang mga hindi malinaw na character", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "I-rate ang extension" }, - "rateExtensionDesc": { - "message": "Paki-isipan ang pagtulong sa amin sa pamamagitan ng isang magandang review!" - }, "browserNotSupportClipboard": { "message": "Hindi suportado ng iyong web browser ang madaling pag-copy ng clipboard. Kopya ito manually sa halip." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Itala ang mga item ng pagkatao sa Tab page para sa madaling auto-fill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Linisin ang clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "BABALA", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Tanggapin ang pag-export ng vault" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Lumipat sa organisasyon" }, - "share": { - "message": "I-share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ lumipat sa $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Ipasok ang 6 na digit na code ng pagpapatunay mula sa iyong authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Ipasok ang 6 na digit na code na na-email sa $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Kung natukoy ang isang form sa pag login, awtomatikong punan kapag naglo load ang web page." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Ang mga nakompromiso o hindi pinagkakatiwalaang mga website ay maaaring samantalahin ang awtomatikong pagpuno sa pag load ng pahina." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Pagkakakilanlan" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Mga Secure Note" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Hilahin", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Kopyahin ang item" }, - "passwordGeneratorPolicyInEffect": { - "message": "Isang o higit pang patakaran ng organisasyon ay nakakaapekto sa iyong mga setting ng generator." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Inilayo na Domain" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Maghanap ng Mga Padala", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Idagdag ang Padala", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Teksto" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Ang pinaka-access count ay nakaabot na", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Paso na" }, - "pendingDeletion": { - "message": "Nakabinbing pagbura" - }, "passwordProtected": { "message": "Protektado ng Password" }, @@ -2456,24 +2462,9 @@ "message": "I-edit ang Ipadala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Anong uri ng Ipadala ang ito?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Isang friendly name upang ilarawan ang Ipadala na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Ang file na gusto mong ipadala." - }, "deletionDate": { "message": "Petsa ng Pagtanggal" }, - "deletionDateDesc": { - "message": "Ang Ipadala ay tatanggalin nang permanente sa tinukoy na petsa at oras.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Petsa ng pag-expire" }, - "expirationDateDesc": { - "message": "Kung nakatakda, ang access sa Send na ito ay mag-expire sa tinukoy na petsa at oras.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 araw" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Pasadyang" }, - "maximumAccessCount": { - "message": "Pinakamataas na Bilang ng Access" - }, - "maximumAccessCountDesc": { - "message": "Kung nakatakda, ang mga user ay hindi na maaaring ma-access ang Send na ito pagkatapos makarating sa maximum access count.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Maipapayo na mag-require ng password para sa mga user na ma-access ang Send na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Pribadong mga tala tungkol sa Send na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "I-deactivate ang Send na ito para hindi na ito ma-access.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopyahin ang link ng Send na ito sa clipboard pagkatapos i-save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Ang teksto na nais mong ipadala." - }, - "sendHideText": { - "message": "Itago ang default na teksto ng Send na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Kasalukuyang access count" - }, "createSend": { "message": "Bagong Ipadala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Bago ka magsimula" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Upang gamitin ang isang calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "mag-click dito", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "upang bumalik sa iyong window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Ang ibinigay na petsa ng pagpaso ay hindi wasto." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Nagkaroon ng error sa pag-save ng iyong mga petsa ng pagbura at pagpaso." }, - "hideEmail": { - "message": "Itago ang aking email address mula sa mga tatanggap." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Isang o higit pang mga patakaran ng organisasyon ay nakaapekto sa iyong mga pagpipilian sa Pagpadala." - }, "passwordPrompt": { "message": "Muling pagsusuri sa master password" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Mali" }, - "regenerateUsername": { - "message": "Muling bumuo ng username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Lumikha ng username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Uri ng username" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus na naka-address na email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Pangalan ng website" }, - "whatWouldYouLikeToGenerate": { - "message": "Ano ang nais mong bumuo?" - }, - "passwordType": { - "message": "Uri ng Password" - }, "service": { "message": "Serbisyo" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Muling ipadala ang abiso" }, - "viewAllLoginOptions": { - "message": "Tingnan ang lahat ng mga pagpipilian sa pag log in" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "Naipadala na ang notification sa iyong device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 6aa0ad6d859..913391d218c 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copier la plaque d'immatriculation" }, + "copyPrivateKey": { + "message": "Copier la clé privée" + }, + "copyPublicKey": { + "message": "Copier la clé publique" + }, + "copyFingerprint": { + "message": "Copier l'empreinte digitale" + }, "copyCustomField": { "message": "Copier $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Saisie automatique de l'identité" }, + "fillVerificationCode": { + "message": "Remplir le code de vérification" + }, + "fillVerificationCodeAria": { + "message": "Remplir le code de vérification", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Générer un mot de passe (copié)" }, @@ -438,9 +454,6 @@ "length": { "message": "Longueur" }, - "passwordMinLength": { - "message": "Longueur minimale du mot de passe" - }, "uppercase": { "message": "Majuscules (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum de caractères spéciaux" }, - "avoidAmbChar": { - "message": "Éviter les caractères ambigus", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Éviter les caractères ambigus", "description": "Label for the avoid ambiguous characters checkbox." @@ -591,7 +600,7 @@ "message": "Ouvrir le site web" }, "launchWebsiteName": { - "message": "Lancer le site Web $ITEMNAME$", + "message": "Lancer le site web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "Noter l'extension" }, - "rateExtensionDesc": { - "message": "Merci de nous aider en mettant une bonne note !" - }, "browserNotSupportClipboard": { "message": "Votre navigateur web ne supporte pas la copie rapide depuis le presse-papier. Copiez-le manuellement à la place." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Liste les éléments d'identité sur la Page d'onglet pour faciliter la saisie automatique." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Effacer le presse-papiers", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "AVERTISSEMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Avertissement", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirmer l'export du coffre" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Déplacer vers l'organisation" }, - "share": { - "message": "Partager" - }, "movedItemToOrg": { "message": "$ITEMNAME$ a été déplacé vers $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Saisissez le code de vérification à 6 chiffres depuis votre application d'authentification." }, + "authenticationTimeout": { + "message": "Délai d'authentification dépassé" + }, + "authenticationSessionTimedOut": { + "message": "La session d'authentification a expiré. Veuillez redémarrer le processus de connexion." + }, "enterVerificationCodeEmail": { "message": "Saisissez le code de vérification à 6 chiffres qui a été envoyé par courriel à $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Si un formulaire de connexion est détecté, il sera saisi automatiquement lors du chargement de la page web." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Attention :$CLOSETAG$ Les sites web compromis ou non fiables peuvent exploiter la saisie automatique lors du chargement de la page.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "les sites web compromis ou non fiables peuvent exploiter la saisie automatique au chargement de la page." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identité" }, + "typeSshKey": { + "message": "Clé SSH" + }, "newItemHeader": { "message": "Nouveau/nouvelle $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Notes sécurisées" }, + "sshKeys": { + "message": "Clés SSH" + }, "clear": { "message": "Effacer", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Cloner" }, - "passwordGeneratorPolicyInEffect": { - "message": "Une ou plusieurs politiques de sécurité de l'organisation affectent les paramètres de votre générateur." - }, "passwordGenerator": { "message": "Générateur de mot de passe" }, @@ -2318,6 +2324,9 @@ "message": "Domaines", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domaines bloqués" + }, "excludedDomains": { "message": "Domaines exclus" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ne demandera pas d'enregistrer les détails de connexion pour ces domaines pour tous les comptes connectés. Vous devez actualiser la page pour que les modifications prennent effet." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Site web $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Changements de domaines exclus enregistrés" }, @@ -2373,14 +2394,6 @@ "message": "Détails du Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Rechercher dans les Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Ajouter un Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Texte" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Masquer le texte par défaut" }, - "maxAccessCountReached": { - "message": "Nombre maximum d'accès atteint", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expiré" }, - "pendingDeletion": { - "message": "En attente de suppression" - }, "passwordProtected": { "message": "Protégé par un mot de passe" }, @@ -2456,24 +2462,9 @@ "message": "Modifier le Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "De quel type de Send s'agit-il ?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nom convivial pour décrire ce Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Le fichier que vous voulez envoyer." - }, "deletionDate": { "message": "Date de suppression" }, - "deletionDateDesc": { - "message": "Le Send sera définitivement supprimé à la date et heure spécifiées.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Le Send sera définitivement supprimé à la date spécifiée.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Date d'expiration" }, - "expirationDateDesc": { - "message": "Si défini, l'accès à ce Send expirera à la date et heure spécifiées.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 jour" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personnalisé" }, - "maximumAccessCount": { - "message": "Nombre maximum d'accès" - }, - "maximumAccessCountDesc": { - "message": "Si défini, les utilisateurs ne seront plus en mesure d'accéder à ce Send une fois que le nombre maximum d'accès sera atteint.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Vous pouvez, si vous le souhaitez, exiger un mot de passe pour accéder à ce Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Ajouter un mot de passe facultatif pour que les destinataires puissent accéder à ce Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Notes privées à propos de ce Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Désactiver ce Send pour que personne ne puisse y accéder.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copier le lien de ce Send dans le presse-papiers lors de l'enregistrement.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Le texte que vous voulez envoyer." - }, - "sendHideText": { - "message": "Masquer le texte de ce Send par défaut.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Nombre d'accès actuel" - }, "createSend": { "message": "Nouveau Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Avant de commencer" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Pour utiliser un sélecteur de date en forme de calendrier,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "cliquez ici", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "pour ouvrir une nouvelle fenêtre.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "La date d'expiration indiquée n'est pas valide." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Une erreur s'est produite lors de l'enregistrement de vos dates de suppression et d'expiration." }, - "hideEmail": { - "message": "Masquer mon adresse électronique aux destinataires." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Une ou plusieurs politiques de sécurité de l'organisation affectent vos options Send." - }, "passwordPrompt": { "message": "Ressaisir le mot de passe principal" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Erreur" }, - "regenerateUsername": { - "message": "Régénérer un nom d'utilisateur" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Générer un nom d'utilisateur" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Générer un courriel" }, - "generatorBoundariesHint": { - "message": "La valeur doit être comprise entre $MIN$ et $MAX$", + "spinboxBoundariesHint": { + "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Type de nom d'utilisateur" + "passwordLengthRecommendationHint": { + "message": " Utilisez $RECOMMENDED$ caractères ou plus pour générer un mot de passe fort.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilisez $RECOMMENDED$ mots ou plus pour générer une phrase de passe 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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Courriel sous-adressé", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nom du site web" }, - "whatWouldYouLikeToGenerate": { - "message": "Que souhaitez-vous générer ?" - }, - "passwordType": { - "message": "Type de mot de passe" - }, "service": { "message": "Service" }, @@ -2936,7 +2894,7 @@ "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Choisissez un domaine qui est supporté par le service sélectionné", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Renvoyer la notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Afficher toutes les options de connexion" + }, + "viewAllLoginOptionsV1": { "message": "Afficher toutes les options de connexion" }, "notificationSentDevice": { "message": "Une notification a été envoyée à votre appareil." }, + "aNotificationWasSentToYourDevice": { + "message": "Une notification a été envoyée à votre appareil" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte correspond à l'autre appareil" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Vous serez notifié une fois que la demande sera approuvée" + }, + "needAnotherOptionV1": { + "message": "Besoin d'une autre option ?" + }, "loginInitiated": { "message": "Connexion initiée" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "S'ouvre dans une nouvelle fenêtre" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Mémorisez cet appareil pour faciliter les futures connexions" + }, "deviceApprovalRequired": { "message": "L'approbation de l'appareil est requise. Sélectionnez une option d'approbation ci-dessous :" }, + "deviceApprovalRequiredV2": { + "message": "Autorisation de l'appareil requise" + }, + "selectAnApprovalOptionBelow": { + "message": "Sélectionnez une option d'approbation ci-dessous" + }, "rememberThisDevice": { "message": "Se souvenir de cet appareil" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Courriel de l'utilisateur manquant" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Courriel utilisateur actif introuvable. Déconnexion en cours." + }, "deviceTrusted": { "message": "Appareil de confiance" }, @@ -3521,6 +3506,14 @@ "message": "Déverrouiller votre compte, s'ouvre dans une nouvelle fenêtre", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Code de vérification de mot de passe unique basé sur le temps", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Temps restant avant l'expiration du TOTP actuel", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Remplir les identifiants pour", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accès en cours" }, + "loggedInExclamation": { + "message": "Connecté !" + }, "passkeyNotCopied": { "message": "La clé d'identification (passkey) ne sera pas copiée" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtres" }, + "filterVault": { + "message": "Filtrer le coffre" + }, + "filterApplied": { + "message": "Un filtre a été appliqué" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtres appliqués", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Données personnelles" }, @@ -4564,13 +4575,13 @@ "message": "Emplacement de l'élément" }, "fileSend": { - "message": "File Send" + "message": "Send en fichier" }, "fileSends": { "message": "File Sends" }, "textSend": { - "message": "Text Send" + "message": "Send en texte" }, "textSends": { "message": "Text Sends" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Afficher le nombre de suggestions de saisie automatique d'identifiant sur l'icône d'extension" }, + "showQuickCopyActions": { + "message": "Afficher les actions de copie rapide dans le coffre" + }, "systemDefault": { "message": "Par défaut" }, "enterprisePolicyRequirementsApplied": { "message": "Les exigences de la politique d'entreprise ont été appliquées à ce paramètre" }, + "sshPrivateKey": { + "message": "Clé privée" + }, + "sshPublicKey": { + "message": "Clé publique" + }, + "sshFingerprint": { + "message": "Empreinte digitale" + }, + "sshKeyAlgorithm": { + "message": "Type de clé" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bits" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bits" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bits" + }, "retry": { "message": "Réessayer" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Vous n'êtes pas autorisé à modifier cet élément" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authentification" }, @@ -4656,7 +4721,7 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Accent grave", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { @@ -4664,27 +4729,27 @@ "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Signe arobase", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Signe dièse", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Signe du dollar", + "message": "Signe dollar", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Signe pourcentage", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Accent circonflexe", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Esperluette", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { @@ -4692,11 +4757,11 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Parenthèse ouvrante", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Parenthèse fermante", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { @@ -4716,23 +4781,23 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Accolade gauche", + "message": "Accolade ouvrante", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Accolade droite", + "message": "Accolade fermante", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Crochet gauche", + "message": "Crochet ouvrante", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Crochet droit", + "message": "Crochet fermante", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Barre", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { @@ -4740,7 +4805,7 @@ "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Deux-points", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Mot de passe généré" + }, + "compactMode": { + "message": "Mode compact" + }, + "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" + }, + "wide": { + "message": "Large" + }, + "extraWide": { + "message": "Très large" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 498dc34f1af..e655159f246 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3,39 +3,39 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Xestor de contrasinais", + "message": "Xestor de Contrasinais Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Na casa, no traballo ou en ruta, Bitwarden protexe os teus contrasinais, chaves de acceso e datos sensíbeis", + "message": "Na casa, no traballo ou en movemento, Bitwarden protexe os teus contrasinais, Claves de acceso e datos sensíbeis", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "Rexístrate ou crea unha nova conta para acceder á túa caixa forte." + "message": "Inicia sesión ou rexístrate para acceder á túa caixa forte." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Invitación aceptada" }, "createAccount": { "message": "Crea unha conta" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novo en Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Iniciar sesión con Clave de acceso" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usar inicio de sesión único" }, "welcomeBack": { - "message": "Welcome back" + "message": "Benvido de novo" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Crea un contrasinal forte" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Remata de crear a túa conta creando un contrasinal" }, "enterpriseSingleSignOn": { "message": "Inicio de sesión único empresarial" @@ -62,7 +62,7 @@ "message": "Unha pista do contrasinal mestre pode axudarte a lembrar o teu contrasinal se o esqueces." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Se esqueces o teu contrasinal, pódese enviar a pista do contrasinal ó teu correo. Máximo de $CURRENT$/$MAXIMUM$ caracteres.", "placeholders": { "current": { "content": "$1", @@ -81,10 +81,10 @@ "message": "Pista do contrasinal mestre (opcional)" }, "joinOrganization": { - "message": "Join organization" + "message": "Unirse a esta organización" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Unirse a $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -93,10 +93,10 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Remata de unirte á organización creando un contrasinal mestre." }, "tab": { - "message": "Separador" + "message": "Pestana" }, "vault": { "message": "Caixa forte" @@ -114,13 +114,13 @@ "message": "Axustes" }, "currentTab": { - "message": "Separador actual" + "message": "Pestana actual" }, "copyPassword": { "message": "Copiar contrasinal" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copiar frase de contrasinal" }, "copyNote": { "message": "Copiar nota" @@ -138,22 +138,31 @@ "message": "Copiar código de seguranza" }, "copyName": { - "message": "Copy name" + "message": "Copiar nome" }, "copyCompany": { - "message": "Copy company" + "message": "Copiar empresa" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Copiar número de Seguridade Social" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copiar número de pasaporte" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copiar número de matrícula" + }, + "copyPrivateKey": { + "message": "Copiar clave privada" + }, + "copyPublicKey": { + "message": "Copiar clave pública" + }, + "copyFingerprint": { + "message": "Copiar pegada dixital" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Copiar $FIELD$", "placeholders": { "field": { "content": "$1", @@ -162,26 +171,33 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Copiar sitio web" }, "copyNotes": { - "message": "Copy notes" + "message": "Copiar notas" }, "fill": { - "message": "Fill", + "message": "Encher", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "Auto-encher" + "message": "Autoencher" }, "autoFillLogin": { - "message": "Encher automaticamente inicio de sesión" + "message": "Autoencher credenciais" }, "autoFillCard": { - "message": "Encher automaticamente tarxeta" + "message": "Autoencher tarxeta" }, "autoFillIdentity": { - "message": "Encher automaticamente identidade" + "message": "Autoencher automaticamente identidade" + }, + "fillVerificationCode": { + "message": "Encher código de verificación" + }, + "fillVerificationCodeAria": { + "message": "Encher código de verificación", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { "message": "Xerar contrasinal (copiado)" @@ -190,7 +206,7 @@ "message": "Copiar nome de campo personalizado" }, "noMatchingLogins": { - "message": "Sen inicios de sesión coincidentes" + "message": "Sen credenciais coincidentes" }, "noCards": { "message": "Sen tarxetas" @@ -199,7 +215,7 @@ "message": "Sen identidades" }, "addLoginMenu": { - "message": "Engadir inicio de sesión" + "message": "Engadir credenciais" }, "addCardMenu": { "message": "Engadir tarxeta" @@ -208,31 +224,31 @@ "message": "Engadir identidade" }, "unlockVaultMenu": { - "message": "Desbloquear a súa caixa forte" + "message": "Desbloquear a caixa forte" }, "loginToVaultMenu": { - "message": "Rexistrarse na súa caixa forte" + "message": "Iniciar sesión na caixa forte" }, "autoFillInfo": { - "message": "Non hai inicios de sesión dispoñíbeis para encher automaticamente para o separador actual do navegador." + "message": "Non hai credenciais dispoñibles para autoenchido na pestana actual." }, "addLogin": { - "message": "Engadir inicio de sesión" + "message": "Engadir unha credencial" }, "addItem": { - "message": "Engadir elemento" + "message": "Engadir entrada" }, "accountEmail": { - "message": "Account email" + "message": "Correo electrónico da conta" }, "requestHint": { - "message": "Request hint" + "message": "Solicitar pista" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Solicitar pista do contrasinal" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "Introduce o teu correo electrónico e enviarémosche a túa pista do contrasinal mestre" }, "passwordHint": { "message": "Pista do contrasinal" @@ -268,29 +284,29 @@ "message": "Continuar á aplicación web?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Descobre máis funcionalidades da túa conta de Bitwarden na aplicación web." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Ir ó Centro de Axuda?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Aprende máis acerca de como usar Bitwarden no Centro de Axuda." }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "Ir á tenda de extensións do navegador?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "Axuda a outros a saber que Bitwarden é o que buscan! Visita a tenda de extensións do navegador e déixanos a túa opinión." }, "changeMasterPasswordOnWebConfirmation": { "message": "Podes cambiar o teu contrasinal mestre na aplicación web de Bitwarden." }, "fingerprintPhrase": { - "message": "Frase de pegada dactilar", + "message": "Frase de pegada dixital", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "Frase de pegada dactilar da túa conta", + "message": "Frase de pegada dixital da túa conta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { @@ -300,43 +316,43 @@ "message": "Pechar sesión" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Acerca de Bitwarden" }, "about": { "message": "Acerca de" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Máis de Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Ir a bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden para Empresas" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "message": "Autenticador 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": "O Autenticador Bitwarden permite almacenar claves de autenticación e xerar códigos de un uso para fluxos de verificación en 2 pasos. Aprende máis en bitwarden.com" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Xestor de Segredos de Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Almacenar, xestionar e compartir de maneira segura segredos de desarrollo co Xestor de Segredos de Bitwarden. Aprende máis en 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": "Crea experiencias de inicio de sesión cómodas e seguras libres de contrasinais tradicionais con Passwordless.dev. Descobre como en bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Plan Familiar Gratuíto Bitwarden" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Es candidato para Bitwarden Familiar Gratuíto. Troca esta oferta hoxe mesmo na aplicación web." }, "version": { "message": "Versión" @@ -357,22 +373,22 @@ "message": "Editar cartafol" }, "newFolder": { - "message": "New folder" + "message": "Novo cartafol" }, "folderName": { - "message": "Folder name" + "message": "Nome do cartafol" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Aniñar un cartafol engadindo o nome do cartafol pai seguido dun \"/\". Exemplo: Social/Foros" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Sen cartafois" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "Crea cartafoles para organizar as entradas da túa caixa forte" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "Estás seguro de querer eliminar permanentemente este cartafol?" }, "deleteFolder": { "message": "Eliminar cartafol" @@ -412,13 +428,13 @@ "description": "Short for 'credential generator'." }, "passGenInfo": { - "message": "Xera automaticamente contrasinais fortes e únicos para os seus inicios de sesión." + "message": "Xera automaticamente contrasinais fortes e únicos para os teus inicios de sesión." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Aplicación web de Bitwarden" }, "importItems": { - "message": "Importar elementos" + "message": "Importar entradas" }, "select": { "message": "Seleccionar" @@ -427,7 +443,7 @@ "message": "Xerar contrasinal" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Xerar frase de contrasinal" }, "regeneratePassword": { "message": "Volver xerar contrasinal" @@ -438,9 +454,6 @@ "length": { "message": "Lonxitude" }, - "passwordMinLength": { - "message": "Lonxitude mínima do contrasinal" - }, "uppercase": { "message": "Maiúsculas (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -458,11 +471,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "include": { - "message": "Include", + "message": "Incluír", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Incluír caracteres en maiúsculas", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -470,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Incluír caracteres en minúsculas", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -478,7 +491,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Incluír números", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -486,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Incluír caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -500,28 +513,24 @@ "message": "Separador de palabras" }, "capitalize": { - "message": "Facer a primeira letra da palabra maiúscula", + "message": "Primeira letra maiúscula", "description": "Make the first letter of a work uppercase." }, "includeNumber": { "message": "Incluír número" }, "minNumbers": { - "message": "Mínimo de números" + "message": "Mínimo de cifras" }, "minSpecial": { "message": "Mínimo de caracteres especiais" }, - "avoidAmbChar": { - "message": "Evitar caracteres ambiguos", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Evitar caracteres ambiguos", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Os requisitos da política empresarial foron aplicados ás opcións do teu xerador.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -534,10 +543,10 @@ "message": "Ver" }, "noItemsInList": { - "message": "Non hai elementos que listar." + "message": "Non hai entradas que listar." }, "itemInformation": { - "message": "Información do elemento" + "message": "Información da entrada" }, "username": { "message": "Nome de usuario" @@ -546,7 +555,7 @@ "message": "Contrasinal" }, "totp": { - "message": "Secreto de autenticador" + "message": "Segredo de autenticador" }, "passphrase": { "message": "Frase de contrasinal" @@ -555,19 +564,19 @@ "message": "Favorito" }, "unfavorite": { - "message": "Unfavorite" + "message": "Suprimir dos favoritos" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Engadido a favoritos" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Eliminado dos favoritos" }, "notes": { "message": "Notas" }, "privateNote": { - "message": "Private note" + "message": "Nota privada" }, "note": { "message": "Nota" @@ -588,10 +597,10 @@ "message": "Iniciar" }, "launchWebsite": { - "message": "Launch website" + "message": "Abrir web" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Abrir web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -615,16 +624,16 @@ "message": "Opcións de desbloqueo" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Configura un método de desbloqueo para cambiar a túa acción de peche de sesión da caixa forte." + "message": "Configura un método de desbloqueo para cambiar a acción do temporizador da caixa forte." }, "unlockMethodNeeded": { "message": "Configura un método de desbloqueo nos axustes" }, "sessionTimeoutHeader": { - "message": "Tempo de sesión esgotado" + "message": "Temporizador da sesión esgotado" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "Temporizador da caixa forte" }, "otherOptions": { "message": "Outras opcións" @@ -632,11 +641,8 @@ "rateExtension": { "message": "Valorar a extensión" }, - "rateExtensionDesc": { - "message": "Por favor, considera axudarnos cunha boa recensión!" - }, "browserNotSupportClipboard": { - "message": "O teu navegador web non soporta copia doada ao portapapeis. Cópiao manualmente no seu lugar." + "message": "O navegador non permite copia doada ó portapapeis. Debes copialo manualmente." }, "verifyIdentity": { "message": "Verificar identidade" @@ -645,19 +651,19 @@ "message": "A túa caixa forte está bloqueada. Verifica a túa identidade para continuar." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "A caixa forte está pechada" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "A conta está pechada" }, "or": { - "message": "or" + "message": "ou" }, "unlock": { "message": "Desbloquear" }, "loggedInAsOn": { - "message": "Conectado coma $EMAIL$ en $HOSTNAME$.", + "message": "Conectado como $EMAIL$ en $HOSTNAME$.", "placeholders": { "email": { "content": "$1", @@ -676,13 +682,13 @@ "message": "Tempo de espera da caixa forte" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Tempo esgotado" }, "lockNow": { - "message": "Bloquear agora" + "message": "Pechar agora" }, "lockAll": { - "message": "Bloquear todo" + "message": "Pechar todo" }, "immediately": { "message": "Inmediatamente" @@ -718,10 +724,10 @@ "message": "4 horas" }, "onLocked": { - "message": "Ao bloquear o sistema" + "message": "Ó bloquear o sistema" }, "onRestart": { - "message": "Ao reiniciar o navegador" + "message": "Ó reiniciar o navegador" }, "never": { "message": "Nunca" @@ -730,16 +736,16 @@ "message": "Seguridade" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Repetir o contrasinal mestre" }, "masterPassword": { - "message": "Master password" + "message": "Contrasinal mestre" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "O contrasinal mestre non pode ser recuperado se o esqueces!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Obter pista do contrasinal mestre" }, "errorOccurred": { "message": "Produciuse un erro" @@ -757,7 +763,7 @@ "message": "É preciso volver escribir o contrasinal mestre." }, "masterPasswordMinlength": { - "message": "O contrasinal mestre debe ter polo menos $VALUE$ caracteres de lonxitude.", + "message": "O contrasinal mestre debe ter polo menos $VALUE$ caracteres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -767,19 +773,19 @@ } }, "masterPassDoesntMatch": { - "message": "A confirmación de contrasinal mestre non coincide." + "message": "O contrasinal mestre repetido non coincide." }, "newAccountCreated": { "message": "A túa nova conta foi creada! Podes iniciar sesión agora." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "A túa nova conta foi creada!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "A túa sesión foi iniciada!" }, "youSuccessfullyLoggedIn": { - "message": "Iniciaches sesión correctamente" + "message": "Sesión iniciada con éxito" }, "youMayCloseThisWindow": { "message": "Podes pechar está ventá" @@ -788,10 +794,10 @@ "message": "Enviámoste un correo electrónico coa túa pista de contrasinal mestre." }, "verificationCodeRequired": { - "message": "É preciso código de verificación." + "message": "É preciso un código de verificación." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "O proceso de autenticación foi cancelado ou levou moito tempo. Por favor, volve intentalo." }, "invalidVerificationCode": { "message": "Código de verificación non válido" @@ -807,28 +813,28 @@ } }, "autofillError": { - "message": "Incapaz de encher automaticamente o elemento seleccionado nesta páxina. Copia e pega a información no seu lugar." + "message": "Non se puido autoencher o formulario nesta páxina. Copia e pega a información manualmente." }, "totpCaptureError": { - "message": "Incapaz escanear o código QR da páxina web actual" + "message": "Non se puido escanear o código QR da páxina web" }, "totpCaptureSuccess": { "message": "Clave de autenticación engadida" }, "totpCapture": { - "message": "Escanea o código QR autenticador da páxina web actual" + "message": "Escanea o código QR do autenticador da páxina web" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Fai a verificación en 2 pasos imperceptible" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden pode gardar e encher códigos de verificación en 2 pasos. Copia e pega a clave neste campo." }, "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 pode gardar e encher códigos de verificación en 2 pasos. Preme a icona da cámara para extraer o código QR do autenticador desta web, ou copia e pega a clave neste campo." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Aprende máis sobre autenticadores." }, "copyTOTP": { "message": "Copiar clave de autenticación (TOTP)" @@ -837,31 +843,31 @@ "message": "Sesión pechada" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "A túa sesión foi pechada." }, "loginExpired": { "message": "A túa sesión caducou." }, "logIn": { - "message": "Log in" + "message": "Iniciar sesión" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Iniciar sesión en Bitwarden" }, "restartRegistration": { - "message": "Restart registration" + "message": "Reiniciar rexistro" }, "expiredLink": { - "message": "Expired link" + "message": "Ligazón caducada" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Por favor, reinicia o rexistro ou tenta iniciar sesión." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Pode que xa teñas unha conta" }, "logOutConfirmation": { - "message": "Are you sure you want to log out?" + "message": "Estás seguro de que queres pechar a sesión?" }, "yes": { "message": "Si" @@ -873,19 +879,19 @@ "message": "Produciuse un erro inesperado." }, "nameRequired": { - "message": "Requírese o nome." + "message": "Nome requirido." }, "addedFolder": { "message": "Cartafol engadido" }, "twoStepLoginConfirmation": { - "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "A verificación en 2 pasos (2FA) fai a túa conta máis segura requirindo confirmar o inicio de sesión con métodos como unha clave de seguridade, aplicación de autenticación, SMS, chamada telefónica ou correo electrónico. A 2FA pode configurarse na aplicación web. Queres ir agora?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Fai a túa conta máis segura activando a verificación en 2 pasos na aplicación web." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Ir á aplicación web?" }, "editedFolder": { "message": "Cartafol gardado" @@ -897,10 +903,10 @@ "message": "Cartafol eliminado" }, "gettingStartedTutorial": { - "message": "Tutorial introdutivo" + "message": "Titorial de Primeiros pasos" }, "gettingStartedTutorialVideo": { - "message": "Watch our getting started tutorial to learn how to get the most out of the browser extension." + "message": "Mira o noso titorial de Primeiros pasos para aprender a sacar partido á extensión de navegador." }, "syncingComplete": { "message": "Sincronización completa" @@ -928,32 +934,32 @@ "message": "Nova URI" }, "addDomain": { - "message": "Add domain", + "message": "Engadir dominio", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { - "message": "Elemento engadido" + "message": "Entrada engadida" }, "editedItem": { - "message": "Elemento gardado" + "message": "Entrada gardada" }, "deleteItemConfirmation": { - "message": "Realmente queres enviar ao lixo?" + "message": "Seguro que queres envialo ó lixo?" }, "deletedItem": { - "message": "Elemento enviado ao lixo" + "message": "Entrada enviada ó lixo" }, "overwritePassword": { "message": "Sobrescribir contrasinal" }, "overwritePasswordConfirmation": { - "message": "Estás seguro de que queres sobrescribir o contrasinal actual?" + "message": "Seguro que queres sobrescribir o contrasinal actual?" }, "overwriteUsername": { "message": "Sobrescribir nome de usuario" }, "overwriteUsernameConfirmation": { - "message": "Estás seguro de que queres sobrescribir o nome de usuario actual?" + "message": "Seguro que queres sobrescribir o nome de usuario actual?" }, "searchFolder": { "message": "Buscar cartafol" @@ -969,103 +975,106 @@ "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "Solicita engadir inicio de sesión" + "message": "Ofrecer gardar credenciais" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "Gardar nas opcións da caixa forte" }, "addLoginNotificationDesc": { - "message": "Ask to add an item if one isn't found in your vault." + "message": "Ofrecer gardar un elemento se non se atopa na caixa forte." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "message": "Ofrecer gardar un elemento se non se atopa na caixa forte. Aplica a tódalas sesións iniciadas." }, "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "message": "Na caixa forte, amosar tarxetas como suxestións de Autoenchido" }, "showCardsCurrentTab": { - "message": "Amosar tarxetas no separador" + "message": "Amosar tarxetas na pestana" }, "showCardsCurrentTabDesc": { - "message": "Lista os elementos de tarxeta no separador para fácil auto-completado." + "message": "Lista na pestana actual as tarxetas gardadas para autoenchido." }, "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "message": "Na caixa forte, amosar identidades como suxestións de Autoenchido" }, "showIdentitiesCurrentTab": { - "message": "Mostrar identidades no separador" + "message": "Amosar identidades na pestana" }, "showIdentitiesCurrentTabDesc": { - "message": "Lista os elementos de identidade no separador para fácil auto-completado." + "message": "Lista na pestana actual as identidades gardadas para autoenchido." + }, + "clickToAutofillOnVault": { + "message": "Clicar nas entradas da caixa forte para autoencher" }, "clearClipboard": { - "message": "Limpar portapapeis", + "message": "Baleirar portapapeis", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "Automatically clear copied values from your clipboard.", + "message": "Limpar automaticamente os valores copiados do teu portapapeis.", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { - "message": "Should Bitwarden remember this password for you?" + "message": "Debería Bitwarden lembrar este contrasinal?" }, "notificationAddSave": { "message": "Gardar" }, "enableChangedPasswordNotification": { - "message": "Ask to update existing login" + "message": "Ofrecer actualizar as credenciais xa gardadas" }, "changedPasswordNotificationDesc": { - "message": "Ask to update a login's password when a change is detected on a website." + "message": "Ofrecer actualizar o contrasinal dunha credencial ó detectar un cambio." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "Ofrecer actualizar o contrasinal dunha credencial ó detectar un cambio. Aplica a tódalas sesións iniciadas." }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "Ofrecer gardar e empregar Claves de acceso" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "Ofrecer gardar e empregar Claves de acceso na caixa forte. Aplica a tódalas sesións iniciadas." }, "notificationChangeDesc": { - "message": "Do you want to update this password in Bitwarden?" + "message": "Queres actualizar este contrasinal en Bitwarden?" }, "notificationChangeSave": { "message": "Actualizar" }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the autofill request." + "message": "Abre a caixa forte de Bitwarden para poder continuar co autoenchido." }, "notificationUnlock": { - "message": "Desbloquear" + "message": "Abrir" }, "additionalOptions": { - "message": "Additional options" + "message": "Opcións adicionais" }, "enableContextMenuItem": { - "message": "Show context menu options" + "message": "Amosar opcións no menú contextual" }, "contextMenuItemDesc": { - "message": "Use a secondary click to access password generation and matching logins for the website." + "message": "Usar o clic dereito para acceder á xeración de contrasinais e autoenchido para a web." }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "Usar o clic dereito para acceder á xeración de contrasinais e autoenchido para a web. Aplica a tódalas sesións iniciadas." }, "defaultUriMatchDetection": { - "message": "Default URI match detection", + "message": "Detección de coincidencias de URI por defecto", "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "Choose the default way that URI match detection is handled for logins when performing actions such as autofill." + "message": "Selecciona o tipo de Detección de coincidencias de URI que se empregará por defecto para accións como o autoenchido." }, "theme": { "message": "Tema" }, "themeDesc": { - "message": "Change the application's color theme." + "message": "Cambiar o tema de cor da aplicación." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "Cambiar o tema de cor da aplicación. Aplica a tódalas sesións iniciadas." }, "dark": { "message": "Escuro", @@ -1080,7 +1089,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "Exportar dende" }, "exportVault": { "message": "Exportar caixa forte" @@ -1089,62 +1098,63 @@ "message": "Formato de ficheiro" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "O arquivo exportado estará protexido e requirirá o contrasinal do arquivo para descifralo." }, "filePassword": { - "message": "File password" + "message": "Contrasinal do arquivo" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Este contrasinal empregarase para exportar e importar o arquivo" }, "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": "Empregar a clave de cifrado da túa conta, derivada do teu nome de usuario e contrasinal, para cifrar o arquivo exportado e limitar o importado á conta de usuario actual." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Crea un contrasinal para cifrar o arquivo exportado e importalo en calquera conta de Bitwarden empregando este contrasinal." }, "exportTypeHeading": { - "message": "Export type" + "message": "Tipo de exportado" }, "accountRestricted": { - "message": "Account restricted" + "message": "Conta restrinxida" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"Contrasinal do arquivo\" e \"Repetir contrasinal do arquivo\" non coinciden." }, "warning": { "message": "ADVERTENCIA", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Advertencia", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { - "message": "Confirm vault export" + "message": "Confirmar o exportado da caixa forte" }, "exportWarningDesc": { - "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + "message": "Esta exportación contén os teus datos nun formato sen cifrar. Non deberías gardar ou enviar o arquivo por canais inseguros (como correo electrónico). Elimínao inmediatamente despois de finalizar o seu uso." }, "encExportKeyWarningDesc": { - "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + "message": "Este tipo de exportado cifra os teus datos empregando a clave de cifrado da túa conta. Se cambias o teu contrasinal ou nome de usuario deberás volver xerar o arquivo." }, "encExportAccountWarningDesc": { - "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + "message": "As claves de cifrado son únicas para cada usuario, polo que non poderás importar un arquivo exportado desta maneira nunha conta distinta." }, "exportMasterPassword": { - "message": "Enter your master password to export your vault data." + "message": "Introduce o teu contrasinal mestre para exportar a caixa forte." }, "shared": { "message": "Compartido" }, "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 para Empresas permite compartir entradas da caixa forte empregando unha organización. Aprende máis en bitwarden.com." }, "moveToOrganization": { - "message": "Move to organization" - }, - "share": { - "message": "Compartir" + "message": "Mover á organización" }, "movedItemToOrg": { - "message": "$ITEMNAME$ moved to $ORGNAME$", + "message": "$ITEMNAME$ moveuse a $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -1157,7 +1167,7 @@ } }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "Escolle a organización á que queres transferir esta entrada. Ó transferila, tamén transfires a súa propiedade á organización. Unha vez transferida, xa non serás o propietario directo desde elemento." }, "learnMore": { "message": "Máis información" @@ -1193,82 +1203,82 @@ "message": "Anexo gardado" }, "file": { - "message": "Ficheiro" + "message": "Arquivo" }, "fileToShare": { - "message": "File to share" + "message": "Arquivo a compartir" }, "selectFile": { - "message": "Selecciona un ficheiro" + "message": "Selecciona un arquivo" }, "maxFileSize": { - "message": "O tamaño máximo de ficheiro é de 500 MB." + "message": "O tamaño máximo é de 500 MB." }, "featureUnavailable": { - "message": "Característica non dispoñible" + "message": "Función non dispoñible" }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "Requírese mudar a clave de cifrado. Por favor, inicia sesión na aplicación web para actualizala." }, "premiumMembership": { - "message": "Membresía de luxo" + "message": "Plan Prémium" }, "premiumManage": { - "message": "Xestionar membresía" + "message": "Xestionar plan" }, "premiumManageAlert": { - "message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Podes xestionar o teu plan na caixa forte web de bitwarden.com. Queres visitala agora?" }, "premiumRefresh": { - "message": "Refresh membership" + "message": "Actualizar o teu plan" }, "premiumNotCurrentMember": { - "message": "You are not currently a Premium member." + "message": "Actualmente non es usuario Prémium." }, "premiumSignUpAndGet": { - "message": "Sign up for a Premium membership and get:" + "message": "Actualiza a unha conta Premium e recibe:" }, "ppremiumSignUpStorage": { - "message": "1 GB encrypted storage for file attachments." + "message": "1 GB de almacenamento cifrado para arquivos anexos." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Acceso de emerxencia." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "Opcións de verificación en 2 pasos privadas tales coma YubiKey ou Duo." }, "ppremiumSignUpReports": { - "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + "message": "Limpeza de contrasinais, saúde de contas e informes de filtración de datos para manter a túa caixa forte segura." }, "ppremiumSignUpTotp": { - "message": "TOTP verification code (2FA) generator for logins in your vault." + "message": "Xerador de códigos TOTP (2FA) para as credenciais da túa caixa forte." }, "ppremiumSignUpSupport": { - "message": "Priority customer support." + "message": "Atención ó cliente prioritaria." }, "ppremiumSignUpFuture": { - "message": "All future Premium features. More coming soon!" + "message": "Tódalas funcións Prémium futuras!" }, "premiumPurchase": { - "message": "Purchase Premium" + "message": "Adquirir Prémium" }, "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Podes adquirir o plan Prémium na aplicación web de bitwarden.com. Queres visitala agora mesmo?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Podes adquirir o plan Prémium dende os axustes de conta da aplicación web de Bitwarden." }, "premiumCurrentMember": { - "message": "You are a Premium member!" + "message": "Xa es un usuario Prémium!" }, "premiumCurrentMemberThanks": { - "message": "Thank you for supporting Bitwarden." + "message": "Grazas por apoiar Bitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "Mellora a Prémium e recibe:" }, "premiumPrice": { - "message": "All for just $PRICE$ /year!", + "message": "Todo por só $PRICE$/ano!", "placeholders": { "price": { "content": "$1", @@ -1277,7 +1287,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "Todo por só $PRICE$ ó ano!", "placeholders": { "price": { "content": "$1", @@ -1286,28 +1296,34 @@ } }, "refreshComplete": { - "message": "Refresh complete" + "message": "Actualización completada" }, "enableAutoTotpCopy": { - "message": "Copy TOTP automatically" + "message": "Copiar TOTP automaticamente" }, "disableAutoTotpCopyDesc": { - "message": "If a login has an authenticator key, copy the TOTP verification code to your clip-board when you autofill the login." + "message": "Se unha credencial ten clave de autenticación, copia o código TOTP no portapapeis ó autoencher os datos." }, "enableAutoBiometricsPrompt": { - "message": "Ask for biometrics on launch" + "message": "Requirir biometría no inicio" }, "premiumRequired": { - "message": "Premium required" + "message": "Plan Prémium requirido" }, "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." + "message": "Requírese un plan Prémium para poder empregar esta función." }, "enterVerificationCodeApp": { - "message": "Enter the 6 digit verification code from your authenticator app." + "message": "Insire o código de 6 díxitos da túa aplicación de autenticación." + }, + "authenticationTimeout": { + "message": "Tempo límite de autenticación superado" + }, + "authenticationSessionTimedOut": { + "message": "Superouse o tempo límite da sesión de autenticación. Recomeza o proceso." }, "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "message": "Insire o código de verificación de 6 díxitos enviado a $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1316,7 +1332,7 @@ } }, "verificationCodeEmailSent": { - "message": "Verification email sent to $EMAIL$.", + "message": "Enviouse un correo de verificación a $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1328,40 +1344,40 @@ "message": "Lémbrame" }, "sendVerificationCodeEmailAgain": { - "message": "Send verification code email again" + "message": "Enviar un novo correo de verificación" }, "useAnotherTwoStepMethod": { - "message": "Use another two-step login method" + "message": "Empregar outro método de verificación en 2 pasos" }, "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." + "message": "Conecta a túa YubiKey no porto USB, despois preme o seu botón." }, "insertU2f": { - "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + "message": "Conecta a túa YubiKey no porto USB. Se ten un botón, prémeo." }, "webAuthnNewTab": { - "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." + "message": "Para iniciar o proceso de verificación WebAuthn 2FA, clica no botón embaixo e segue as instrucións na pestana que se abrirá." }, "webAuthnNewTabOpen": { - "message": "Abrir novo separador" + "message": "Abrir nova pestana" }, "webAuthnAuthenticate": { "message": "Autenticar con WebAuthn" }, "loginUnavailable": { - "message": "Inicio de sesión non dispoñíbel" + "message": "Inicio de sesión non dispoñible" }, "noTwoStepProviders": { - "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this web browser." + "message": "Esta conta ten activada a verificación en 2 pasos, pero ningún dos métodos dispoñibles é compatible con este navegador." }, "noTwoStepProviders2": { - "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + "message": "Por favor, emprega un navegador compatible (como Chrome) ou un método de maior compatibilidade con distintos navegadores (como unha aplicación de autenticación)." }, "twoStepOptions": { - "message": "Opcións de inicio de sesión en dous pasos" + "message": "Opcións de verificación en dous pasos" }, "recoveryCodeDesc": { - "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." + "message": "Perdiche o acceso ós provedores de verificación en dous pasos (2FA)? Emprega o teu código de recuperación para desactivalos." }, "recoveryCodeTitle": { "message": "Código de recuperación" @@ -1370,61 +1386,61 @@ "message": "Aplicación de autenticación" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Insire un código xerado por unha aplicación de autenticación coma Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "Clave de seguridade OTP de Yubico" }, "yubiKeyDesc": { - "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + "message": "Emprega YubiKey para acceder á túa conta. Funciona con dispositivos YubiKey 4, 4 Nano, 4C e Neo." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Emprega un código xerado por Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "Protexe a túa organización con Duo Security ca app móbil Duo Mobile, SMS, chamada telefónica ou chave de seguridade U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn compatible security key to access your account." + "message": "Emprega unha clave de seguridade compatible con WebAuthn para acceder á túa conta." }, "emailTitle": { "message": "Correo electrónico" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Insire o código enviado ó teu correo electrónico." }, "selfHostedEnvironment": { - "message": "Entorno de auto-aloxamento" + "message": "Entorno de aloxamento propio" }, "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." + "message": "Especifica a URL base do teu servidor Bitwarden de aloxamento propio." }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Especifica a URL base do teu servidor Bitwarden de aloxamento propio. Ex.: https://bitwarden.compañia.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Para configuración avanzada, específica de forma independente a URL base de cada servizo." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Debes engadir ou a URL base do servidor ou, polo menos, un entorno personalizado." }, "customEnvironment": { "message": "Entorno personalizado" }, "customEnvironmentFooter": { - "message": "Para usuarios avanzados. Poder especificar o URL base de cada servizo de xeito independente." + "message": "Para usuarios avanzados. Podes especificar o URL base de cada servizo de xeito independente." }, "baseUrl": { "message": "URL do servidor" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL do servidor de aloxamento propio", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1446,114 +1462,101 @@ "message": "URLs do entorno gardadas" }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "Amosar menú de autoenchido en formularios", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Suxestións de autoenchido" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "Amosar suxestións de autoenchido en formularios" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Amosar identidades como suxestións" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Amosar tarxetas como suxestións" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Amosar as suxestións ó premer a icona de Bitwarden" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Aplica a tódalas sesións iniciadas." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "Desactiva o xestor de contrasinais do teu navegador para evitar conflitos." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "Cambiar axustes do navegador." }, "autofillOverlayVisibilityOff": { "message": "Apagado", "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "Cando o campo está seleccionado (activo)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "Cando a icona de autoenchido está seleccionada", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Autoencher ó cargar a páxina" }, "enableAutoFillOnPageLoad": { - "message": "Autofill on page load" + "message": "Autoencher ó cargar a páxina" }, "enableAutoFillOnPageLoadDesc": { - "message": "If a login form is detected, autofill when the web page loads." - }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } + "message": "Se se detecta un formulario de inicio de sesión, autoenchelo ó cargar a páxina." }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit autofill on page load." + "message": "As webs comprometidas ou non fiables poden aproveitarse do autoenchido ó cargar a páxina." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Saber máis dos riscos" }, "learnMoreAboutAutofill": { - "message": "Learn more about autofill" + "message": "Saber máis do autoenchido" }, "defaultAutoFillOnPageLoad": { - "message": "Default autofill setting for login items" + "message": "Axustes de autoenchido por defecto para as credenciais" }, "defaultAutoFillOnPageLoadDesc": { - "message": "You can turn off autofill on page load for individual login items from the item's Edit view." + "message": "Podes apagar o autoenchido para entradas concretas nas súas páxinas de edición." }, "itemAutoFillOnPageLoad": { - "message": "Autofill on page load (if set up in Options)" + "message": "Autoenchido ó cargar a páxina (se está activado nos Axustes)" }, "autoFillOnPageLoadUseDefault": { - "message": "Use default setting" + "message": "Usar axustes por defecto" }, "autoFillOnPageLoadYes": { - "message": "Autofill on page load" + "message": "Autoencher ó cargar a páxina" }, "autoFillOnPageLoadNo": { - "message": "Do not autofill on page load" + "message": "Non autoencher ó cargar a páxina" }, "commandOpenPopup": { - "message": "Open vault popup" + "message": "Abrir ventá emerxente da caixa forte" }, "commandOpenSidebar": { - "message": "Open vault in sidebar" + "message": "Abrir caixa forte na barra lateral" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Autoencher a última credencial empregada nesta web" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Autoencher a última tarxeta empregada nesta web" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Autoencher a última identidade empregada nesta web" }, "commandGeneratePasswordDesc": { - "message": "Generate and copy a new random password to the clipboard" + "message": "Xerar e copiar un novo contrasinal aleatorio ó portapapeis" }, "commandLockVaultDesc": { - "message": "Lock the vault" + "message": "Cerrar a caixa forte" }, "customFields": { "message": "Campos personalizados" @@ -1580,7 +1583,7 @@ "message": "Booleano" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Caixa" }, "cfTypeLinked": { "message": "Vinculado", @@ -1591,28 +1594,28 @@ "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "Clicking outside the popup window to check your email for your verification code will cause this popup to close. Do you want to open this popup in a new window so that it does not close?" + "message": "Se clicas fóra desta ventá emerxente para comprobar o código do correo, cerrarase. Queres convertila nunha nova ventá completa para que non se cerre?" }, "popupU2fCloseMessage": { - "message": "This browser cannot process U2F requests in this popup window. Do you want to open this popup in a new window so that you can log in using U2F?" + "message": "Este navegador non pode procesar peticións U2F nesta ventá emerxente. Queres convertila nunha ventá completa para poder continuar?" }, "enableFavicon": { - "message": "Show website icons" + "message": "Amosar iconas web" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "Amosar unha imaxe recoñecible xunto a cada credencial." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Amosar unha imaxe recoñecible xunto a cada credencial. Aplica a tódalas sesións iniciadas en Bitwarden." }, "enableBadgeCounter": { - "message": "Show badge counter" + "message": "Amosar contador na icona" }, "badgeCounterDesc": { - "message": "Indicate how many logins you have for the current web page." + "message": "Indicar cantas credenciais tes gardadas para a web aberta." }, "cardholderName": { - "message": "Nome do titular da tarxeta" + "message": "Titular da tarxeta" }, "number": { "message": "Número" @@ -1621,7 +1624,7 @@ "message": "Marca" }, "expirationMonth": { - "message": "Mes de expiración" + "message": "Mes de vencemento" }, "expirationYear": { "message": "Ano de vencemento" @@ -1687,7 +1690,7 @@ "message": "Dr" }, "mx": { - "message": "Mx" + "message": "Srx" }, "firstName": { "message": "Nome" @@ -1702,19 +1705,19 @@ "message": "Nome completo" }, "identityName": { - "message": "Nome de identidade" + "message": "Nome da identidade" }, "company": { "message": "Empresa" }, "ssn": { - "message": "Número da seguridade social" + "message": "Número da Seguridade Social" }, "passportNumber": { "message": "Número de pasaporte" }, "licenseNumber": { - "message": "Número de licencia" + "message": "Número de matrícula" }, "email": { "message": "Correo electrónico" @@ -1753,7 +1756,7 @@ "message": "Inicio de sesión" }, "typeLogins": { - "message": "Inicios se sesión" + "message": "Inicios de sesión" }, "typeSecureNote": { "message": "Nota segura" @@ -1764,8 +1767,11 @@ "typeIdentity": { "message": "Identidade" }, + "typeSshKey": { + "message": "Clave SSH" + }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Novo $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1774,7 +1780,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Modificar $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1783,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Ver $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1795,13 +1801,13 @@ "message": "Historial de contrasinais" }, "generatorHistory": { - "message": "Generator history" + "message": "Historial do xerador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Baleirar historial do xerador" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Se continúas, tódalas entradas serán eliminadas permanentemente do historial do xerador. Seguro que queres continuar?" }, "back": { "message": "Atrás" @@ -1810,7 +1816,7 @@ "message": "Coleccións" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ coleccións", "placeholders": { "count": { "content": "$1", @@ -1822,7 +1828,7 @@ "message": "Favoritos" }, "popOutNewWindow": { - "message": "Pop out to a new window" + "message": "Sacar a unha nova ventá" }, "refresh": { "message": "Actualizar" @@ -1834,20 +1840,23 @@ "message": "Identidades" }, "logins": { - "message": "Inicios de sesión" + "message": "Credenciais" }, "secureNotes": { "message": "Notas seguras" }, + "sshKeys": { + "message": "Claves SSH" + }, "clear": { "message": "Limpar", "description": "To clear something out. example: To clear browser history." }, "checkPassword": { - "message": "Check if password has been exposed." + "message": "Comprobar se o contrasinal foi filtrado." }, "passwordExposed": { - "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "message": "Este contrasinal foi filtrado $VALUE$ vez/veces. Deberías cambialo.", "placeholders": { "value": { "content": "$1", @@ -1856,14 +1865,14 @@ } }, "passwordSafe": { - "message": "This password was not found in any known data breaches. It should be safe to use." + "message": "Este contrasinal non foi atopado en ningunha filtración de datos. Debería ser seguro." }, "baseDomain": { "message": "Dominio base", "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Dominio base (recomendado)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1871,7 +1880,7 @@ "description": "Domain name. Ex. website.com" }, "host": { - "message": "Anfitrión", + "message": "Servidor", "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { @@ -1885,18 +1894,18 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Match detection", + "message": "Detección de coincidencias", "description": "URI match detection for autofill." }, "defaultMatchDetection": { - "message": "Default match detection", + "message": "Detección de coincidencias por defecto", "description": "Default URI match detection for autofill." }, "toggleOptions": { - "message": "Toggle options" + "message": "Alternar opcións" }, "toggleCurrentUris": { - "message": "Toggle current URIs", + "message": "Alternar URIs actuais", "description": "Toggle the display of the URIs of the currently open tabs in the browser." }, "currentUri": { @@ -1911,19 +1920,19 @@ "message": "Tipos" }, "allItems": { - "message": "Todos os elementos" + "message": "Todas as entradas" }, "noPasswordsInList": { "message": "Non hai contrasinais que listar." }, "clearHistory": { - "message": "Clear history" + "message": "Baleirar historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Nada que amosar" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Non xerache nada recentemente" }, "remove": { "message": "Eliminar" @@ -1944,26 +1953,26 @@ "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "Seguro que queres marcar a opción \"Nunca\"? Isto almacenará a clave de cifrado da túa caixa forte no teu dispositivo. Se usas esta opción debes asegurarte de ter o dispositivo ben protexido." }, "noOrganizationsList": { - "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + "message": "Non pertences a ningunha organización. As organizacións permiten compartir entradas con outros usuarios." }, "noCollectionsInList": { - "message": "There are no collections to list." + "message": "Non hai coleccións que listar." }, "ownership": { "message": "Propiedade" }, "whoOwnsThisItem": { - "message": "Quen posúe este elemento?" + "message": "Quen posúe esta entrada?" }, "strong": { "message": "Forte", "description": "ex. A strong password. Scale: Weak -> Good -> Strong" }, "good": { - "message": "Boa", + "message": "Bo", "description": "ex. A good password. Scale: Weak -> Good -> Strong" }, "weak": { @@ -1974,94 +1983,91 @@ "message": "Contrasinal mestre feble" }, "weakMasterPasswordDesc": { - "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + "message": "O contrasinal mestre creado é feble. Debes empregar un contrasinal ou frase de contrasinal mestre forte para protexer debidamente a túa conta. Seguro que queres continuar con este contrasinal?" }, "pin": { "message": "PIN", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Abrir con PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "Definir PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "Crear PIN" }, "setYourPinCode": { - "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." + "message": "Crea un PIN para abrir a caixa forte. Se algunha vez pechas a sesión en Bitwarden perderase a configuración deste PIN." }, "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": "O PIN empregarase no lugar do contrasinal mestre para abrir a caixa forte. Se pechas a sesión en Bitwarden perderase a configuración do PIN." }, "pinRequired": { - "message": "PIN code is required." + "message": "PIN requirido." }, "invalidPin": { - "message": "Invalid PIN code." + "message": "PIN incorrecto." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Múltiples intentos fallidos de PIN. Pechando a sesión." }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Abrir con biometría" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Abrir con contrasinal mestre" }, "awaitDesktop": { - "message": "Awaiting confirmation from desktop" + "message": "Agardando pola confirmación do escritorio" }, "awaitDesktopDesc": { - "message": "Please confirm using biometrics in the Bitwarden desktop application to set up biometrics for browser." + "message": "Por favor confirma o uso de biometría na aplicación de escritorio de Bitwarden para activar a biometría no navegador." }, "lockWithMasterPassOnRestart": { - "message": "Lock with master password on browser restart" + "message": "Bloquear con contrasinal mestre ó pechar o navegador" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Requirir contrasinal mestre ó abrir o navegador" }, "selectOneCollection": { - "message": "You must select at least one collection." + "message": "Debes seleccionar polo menos unha colección." }, "cloneItem": { - "message": "Clonar elemento" + "message": "Duplicar entrada" }, "clone": { "message": "Clonar" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { - "message": "Password generator" + "message": "Xerador de contrasinais" }, "usernameGenerator": { - "message": "Username generator" + "message": "Xerador de nomes de usuario" }, "useThisPassword": { - "message": "Use this password" + "message": "Usar este contrasinal" }, "useThisUsername": { - "message": "Use this username" + "message": "Usar este nome de usuario" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "Contrasinal seguro xerado! Non esquezas actualizar o contrasinal na web." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Usar o xerador", "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": "para crear un contrasinal forte", "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'" }, "vaultTimeoutAction": { - "message": "Vault timeout action" + "message": "Acción do temporizador da caixa forte" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Acción do temporizador" }, "lock": { "message": "Bloquear", @@ -2072,55 +2078,55 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Search trash" + "message": "Buscar no lixo" }, "permanentlyDeleteItem": { - "message": "Permanently delete item" + "message": "Eliminar entrada permanentemente" }, "permanentlyDeleteItemConfirmation": { - "message": "Are you sure you want to permanently delete this item?" + "message": "Seguro que queres eliminar permanentemente esta entrada?" }, "permanentlyDeletedItem": { - "message": "Item permanently deleted" + "message": "Entrada eliminada permanente" }, "restoreItem": { - "message": "Restore item" + "message": "Restaurar entrada" }, "restoredItem": { - "message": "Item restored" + "message": "Entrada restaurada" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Xa tes unha conta?" }, "vaultTimeoutLogOutConfirmation": { - "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + "message": "Cerrar a sesión despois dun tempo elimina o acceso á caixa forte ata que se volva iniciar sesión. Seguro que queres empregar esta opción?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Timeout action confirmation" + "message": "Confirmación de acción do temporizador" }, "autoFillAndSave": { - "message": "Autofill and save" + "message": "Autoencher e gardar" }, "fillAndSave": { - "message": "Fill and save" + "message": "Encher e gardar" }, "autoFillSuccessAndSavedUri": { - "message": "Item autofilled and URI saved" + "message": "Entrada autoenchida e URI gardada" }, "autoFillSuccess": { - "message": "Item autofilled " + "message": "Entrada autoenchida " }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "Aviso: esta é unha páxina web HTTP sen cifrar, e calquera información que envíes é visible para terceiros. A credencial foi orixinalmente gardada nunha web HTTPS cifrada." }, "insecurePageWarningFillPrompt": { - "message": "Do you still wish to fill this login?" + "message": "Aínda queres autoencher esta credencial?" }, "autofillIframeWarning": { - "message": "The form is hosted by a different domain than the URI of your saved login. Choose OK to autofill anyway, or Cancel to stop." + "message": "O formulario está aloxado nun dominio diferente da URI que gardaches. Clica Aceptar para autoencher igualmente, ou Cancelar para parar." }, "autofillIframeWarningTip": { - "message": "To prevent this warning in the future, save this URI, $HOSTNAME$, to your Bitwarden login item for this site.", + "message": "Para prever este aviso no futuro, garda este URI, $HOSTNAME$, na túa entrada en Bitwarden para este sitio.", "placeholders": { "hostname": { "content": "$1", @@ -2129,22 +2135,22 @@ } }, "setMasterPassword": { - "message": "Set master password" + "message": "Definir contrasinal mestre" }, "currentMasterPass": { - "message": "Current master password" + "message": "Contrasinal mestre actual" }, "newMasterPass": { - "message": "New master password" + "message": "Novo contrasinal mestre" }, "confirmNewMasterPass": { - "message": "Confirm new master password" + "message": "Repetir novo contrasinal mestre" }, "masterPasswordPolicyInEffect": { - "message": "One or more organization policies require your master password to meet the following requirements:" + "message": "As directivas da túa organización esixen que o teu contrasinal mestre cumpra os seguintes requisitos:" }, "policyInEffectMinComplexity": { - "message": "Minimum complexity score of $SCORE$", + "message": "Complexidade mínima de $SCORE$ puntos", "placeholders": { "score": { "content": "$1", @@ -2153,7 +2159,7 @@ } }, "policyInEffectMinLength": { - "message": "Minimum length of $LENGTH$", + "message": "Mínimo de $LENGTH$ caracteres", "placeholders": { "length": { "content": "$1", @@ -2162,16 +2168,16 @@ } }, "policyInEffectUppercase": { - "message": "Contain one or more uppercase characters" + "message": "Conter polo menos unha maiúscula" }, "policyInEffectLowercase": { - "message": "Contain one or more lowercase characters" + "message": "Conter polo menos unha minúscula" }, "policyInEffectNumbers": { - "message": "Contain one or more numbers" + "message": "Conter polo menos un número" }, "policyInEffectSpecial": { - "message": "Contain one or more of the following special characters $CHARS$", + "message": "Conter polo menos un dos seguintes caracteres: $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -2180,155 +2186,167 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Your new master password does not meet the policy requirements." + "message": "Este contrasinal mestre non cumpre cas directivas." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Recibe consellos, anuncios e oportunidades de investigación de Bitwarden no teu correo." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Cancelar subscrición" }, "atAnyTime": { - "message": "at any time." + "message": "en calquera momento." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ó continuar, estarás aceptando os" }, "and": { - "message": "and" + "message": "e" }, "acceptPolicies": { - "message": "By checking this box you agree to the following:" + "message": "Ó marcar esta caixa aceptas o seguinte:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "Os Termos de Servizo e a Política de Privacidade non foron aceptados." }, "termsOfService": { - "message": "Terms of Service" + "message": "Termos de Servizo" }, "privacyPolicy": { - "message": "Privacy Policy" + "message": "Política de Privacidade" }, "hintEqualsPassword": { - "message": "Your password hint cannot be the same as your password." + "message": "A pista do contrasinal non pode ser o contrasinal." }, "ok": { - "message": "Ok" + "message": "Aceptar" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Erro de actualización do Token de Acceso" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Non se atoparon token de actualización nin claves da API. Por favor trata de volver iniciar sesión." }, "desktopSyncVerificationTitle": { - "message": "Desktop sync verification" + "message": "Verificación de sincronización co escritorio" }, "desktopIntegrationVerificationText": { - "message": "Please verify that the desktop application shows this fingerprint: " + "message": "Por favor verifica que a app de escritorio amosa esta pegada dixital: " }, "desktopIntegrationDisabledTitle": { - "message": "Browser integration is not set up" + "message": "Integración co navegador non configurada" }, "desktopIntegrationDisabledDesc": { - "message": "Browser integration is not set up in the Bitwarden desktop application. Please set it up in the settings within the desktop application." + "message": "A integración co navegador non está activada na app de escritorio de Bitwarden. Por favor configúraa dende os axustes da app de escritorio." }, "startDesktopTitle": { - "message": "Start the Bitwarden desktop application" + "message": "Abrir a app de escritorio de Bitwarden" }, "startDesktopDesc": { - "message": "The Bitwarden desktop application needs to be started before unlock with biometrics can be used." + "message": "Para poder empregar a biometría a aplicación de escritorio de Bitwarden debe estar executada." }, "errorEnableBiometricTitle": { - "message": "Unable to set up biometrics" + "message": "Non foi posible activar a biometría" }, "errorEnableBiometricDesc": { - "message": "Action was canceled by the desktop application" + "message": "Proceso interrompido pola aplicación de escritorio" }, "nativeMessagingInvalidEncryptionDesc": { - "message": "Desktop application invalidated the secure communication channel. Please retry this operation" + "message": "A aplicación de escritorio invalidou a canle de comunicación segura. Por favor volve intentalo" }, "nativeMessagingInvalidEncryptionTitle": { - "message": "Desktop communication interrupted" + "message": "Comunicación co escritorio interrompida" }, "nativeMessagingWrongUserDesc": { - "message": "The desktop application is logged into a different account. Please ensure both applications are logged into the same account." + "message": "A aplicación de escritorio ten unha conta distinta. Por favor asegúrate de que ambas aplicacións iniciaron sesión na mesma conta." }, "nativeMessagingWrongUserTitle": { - "message": "Account missmatch" + "message": "Conta non coincidente" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "Clave biométrica non coincidente" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "Desbloqueo biométrico fallido. A clave biométrica non puido abrir a caixa forte. Por favor proba a volver configurar a biometría." }, "biometricsNotEnabledTitle": { - "message": "Biometrics not set up" + "message": "Biometría non activada" }, "biometricsNotEnabledDesc": { - "message": "Browser biometrics requires desktop biometric to be set up in the settings first." + "message": "A biometría no navegador require que se configure primeiro na app de escritorio." }, "biometricsNotSupportedTitle": { - "message": "Biometrics not supported" + "message": "Biometría non compatible" }, "biometricsNotSupportedDesc": { - "message": "Browser biometrics is not supported on this device." + "message": "Biometría do navegador non compatible con este dispositivo." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Usuario cerrado ou desconectado" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Por favor desbloquea o usuario na aplicación de escritorio e volve intentalo." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "Desbloqueo biométrico non dispoñible" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "Desbloqueo biométrico non dispoñible neste momento. Volve intentalo máis tarde." }, "biometricsFailedTitle": { - "message": "Biometrics failed" + "message": "Fallo da biometría" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "A biometría non puido ser completada. Proba a usar o contrasinal mestre ou pechar a sesión. Se o problema persiste, contacta con atención ó cliente." }, "nativeMessaginPermissionErrorTitle": { - "message": "Permission not provided" + "message": "Permiso non concedido" }, "nativeMessaginPermissionErrorDesc": { - "message": "Without permission to communicate with the Bitwarden Desktop Application we cannot provide biometrics in the browser extension. Please try again." + "message": "Sen permiso de comunicación ca aplicación de escritorio non podemos prover biometría na extensión de navegador. Por favor, volve intentalo." }, "nativeMessaginPermissionSidebarTitle": { - "message": "Permission request error" + "message": "Error de petición de permisos" }, "nativeMessaginPermissionSidebarDesc": { - "message": "This action cannot be done in the sidebar, please retry the action in the popup or popout." + "message": "Esta acción non pode ser realizada na barra lateral. Inténtao dende a ventá emerxente." }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available collections." + "message": "Debido a unha directiva da empresa, non podes gardar entradas na túa caixa forte. Cambia a opción de propiedade a unha organización e elixe unha das coleccións dispoñibles." }, "personalOwnershipPolicyInEffect": { - "message": "An organization policy is affecting your ownership options." + "message": "Unha directiva da empresa está a afectar ás túas opcións de propiedade." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "Unha directiva da empresa impide importar entradas á túa caixa forte individual." }, "domainsTitle": { - "message": "Domains", + "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { - "message": "Excluded domains" + "message": "Dominios excluídos" }, "excludedDomainsDesc": { - "message": "Bitwarden will not ask to save login details for these domains. You must refresh the page for changes to take effect." + "message": "Bitwarden non ofrecerá gardar contas para estes dominios. Recarga a páxina para que os cambios fagan efecto." }, "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 non ofrecerá gardar contas para estes dominios en ningunha das sesións iniciadas. Recarga a páxina para que os cambios fornezan efecto." + }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "Web $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2337,7 +2355,7 @@ } }, "excludedDomainsInvalidDomain": { - "message": "$DOMAIN$ is not a valid domain", + "message": "$DOMAIN$ non é un dominio válido", "placeholders": { "domain": { "content": "$1", @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "Dominios excluídos gardados" }, "limitSendViews": { - "message": "Limit views" + "message": "Limitar visionados" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Ninguén pode ver este Send dende que se alcance o límite.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ visionados restantes", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2370,22 +2391,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "searchSends": { - "message": "Buscar Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Engadir Send", + "message": "Detalles do Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "Texto" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Texto a compartir" }, "sendTypeFile": { "message": "Ficheiro" @@ -2395,36 +2408,29 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" - }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + "message": "Ocultar texto por defecto" }, "expired": { - "message": "Caducado" - }, - "pendingDeletion": { - "message": "Pending deletion" + "message": "Vencido" }, "passwordProtected": { - "message": "Password protected" + "message": "Protexido con contrasinal" }, "copyLink": { - "message": "Copy link" + "message": "Copiar ligazón" }, "copySendLink": { "message": "Copiar ligazón Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "Remove Password" + "message": "Eliminar contrasinal" }, "delete": { "message": "Eliminar" }, "removedPassword": { - "message": "Password removed" + "message": "Contrasinal eliminado" }, "deletedSend": { "message": "Send eliminado", @@ -2435,55 +2441,36 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { - "message": "Disabled" + "message": "Deshabilitado" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "Seguro que queres eliminar o contrasinal?" }, "deleteSend": { "message": "Eliminar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", + "message": "Seguro que queres eliminar este Send?", "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": "Seguro que queres eliminar permanentemente este Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { "message": "Editar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { - "message": "Deletion date" - }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + "message": "Data de eliminación" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "Este Send será permanente eliminado nesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Expiration date" - }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + "message": "Data de vencemento" }, "oneDay": { "message": "1 día" @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalizado" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "Engade un contrasinal opcional para os destinatarios deste Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "Novo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2549,7 +2503,7 @@ "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": "Por unha directiva de empresa só podes eliminar un Send existente.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { @@ -2557,15 +2511,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": "Send creado con éxito!", "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": "O Send estará dispoñible para calquera ca ligazón durante 1 hora.", "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": "O Send estará dispoñible para calquera ca ligazón durante $HOURS$ horas.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2575,11 +2529,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "O Send estará dispoñible para calquera ca ligazón durante 1 día.", "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": "O Send estará dispoñible para calquera ca ligazón durante $DAYS$ días.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2589,7 +2543,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Ligazón do Send copiado", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2597,120 +2551,102 @@ "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": "Sacar a extensión?", "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": "Para crear un arquivo Send, necesitas sacar a extensión a unha nova ventá.", "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": "Para escoller un arquivo, abre a extensión na barra lateral (se é posible) ou sácaa a unha nova ventá premendo este botón." }, "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": "Para escoller un arquivo empregando Firefox, abre a extensión na barra lateral ou sácaa a unha nova ventá premendo este botón." }, "sendSafariFileWarning": { - "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + "message": "Para escoller un arquivo empregando Safari, saca a extensión a unha nova ventá premendo este botón." }, "popOut": { - "message": "Pop out" + "message": "Sacar" }, "sendFileCalloutHeader": { - "message": "Before you start" - }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" + "message": "Antes de comezar" }, "expirationDateIsInvalid": { - "message": "The expiration date provided is not valid." + "message": "A data de vencemento non é válida." }, "deletionDateIsInvalid": { - "message": "The deletion date provided is not valid." + "message": "A data de eliminación non é válida." }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "Unha data e hora de vencemento son requiridas." }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "Unha data e hora de eliminación son requiridas." }, "dateParsingError": { - "message": "There was an error saving your deletion and expiration dates." - }, - "hideEmail": { - "message": "Hide my email address from recipients." + "message": "Produciuse un erro ó gardar as datas de vencemento e eliminación." }, "hideYourEmail": { - "message": "Hide your email address from viewers." - }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." + "message": "Oculta o teu enderezo electrónico ós visitantes." }, "passwordPrompt": { - "message": "Master password re-prompt" + "message": "Volver solicitar o contrasinal mestre" }, "passwordConfirmation": { - "message": "Master password confirmation" + "message": "Repetir o contrasinal mestre" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "Esta acción está protexida. Para continuar por favor volve introducir o teu contrasinal mestre." }, "emailVerificationRequired": { - "message": "Email verification required" + "message": "Verificación do correo electrónico requirida" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Correo electrónico verificado" }, "emailVerificationRequiredDesc": { - "message": "You must verify your email to use this feature. You can verify your email in the web vault." + "message": "Debes verificar o teu correo electrónico para empregar esta función. Podes facelo dende a aplicación web." }, "updatedMasterPassword": { - "message": "Updated master password" + "message": "Contrasinal mestre actualizado" }, "updateMasterPassword": { - "message": "Update master password" + "message": "Actualizar contrasinal mestre" }, "updateMasterPasswordWarning": { - "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update it 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": "O teu contrasinal mestre foi recentemente cambiado por un administrador da túa organización. Para poder acceder á caixa forte debes actualizalo. A continuación cerrarase a túa sesión, requirindo volver iniciala. As sesións activas noutros dispositivos poden permanecer activas por unha hora." }, "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": "O teu contrasinal mestre non cumpre algún dos requisitos das directivas da empresa. Para poder acceder a caixa forte debes actualizar o teu contrasinal mestre. A continuación cerrarase a túa sesión, requirindo volver iniciala. As sesións activas noutros dispositivos poden permanecer activas por unha hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "A túa organización desactivou cifrado en dispositivos de confianza. Por favor, crea un contrasinal mestre para acceder á túa caixa forte." }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic enrollment" + "message": "Rexistro automático" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + "message": "Esta organización ten unha directiva de empresa que te rexistrará automaticamente nun restablecemento de contrasinal. O rexistro permitirá a organización cambiar o teu contrasinal mestre." }, "selectFolder": { - "message": "Select folder..." + "message": "Seleccionar cartafol..." }, "noFoldersFound": { - "message": "No folders found", + "message": "Cartafol non atopado", "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": "Os permisos da organización foron actualizados, requirindo que crees un contrasinal mestre.", "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": "A túa urbanización require que crees un contrasinal mestre.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "de $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2719,7 +2655,7 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "Verificación requirida", "description": "Default title for the user verification dialog." }, "hours": { @@ -2729,10 +2665,10 @@ "message": "Minutos" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Directivas da empresa foron aplicadas ás opcións do temporizador da túa Caixa forte" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "Directivas da empresa restrinxen o temporizador da túa Caixa forte a un máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -2745,7 +2681,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ hora(s) e $MINUTES$ minuto(s) de máximo.", "placeholders": { "hours": { "content": "$1", @@ -2758,7 +2694,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "O tempo indicado excede as restricións definidas pola túa organización: un máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", "placeholders": { "hours": { "content": "$1", @@ -2771,7 +2707,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": "As directivas da empresa restrinxen o temporizador da túa Caixa forte a $HOURS$ hora(s) e $MINUTES$ minuto(s) de máximo. A acción a realizar será: $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2788,7 +2724,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "As directivas da empresa definiron o temporizador da túa Caixa forte en $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2797,22 +2733,22 @@ } }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "O temporizador da túa Caixa forte excede as restricións definidas pola túa organización." }, "vaultExportDisabled": { - "message": "Vault export unavailable" + "message": "Exportado da Caixa forte non dispoñible" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your individual vault." + "message": "Directivas da empresa impiden exportar a túa caixa forte individual." }, "copyCustomFieldNameInvalidElement": { - "message": "Unable to identify a valid form element. Try inspecting the HTML instead." + "message": "No se puido identificar un elemento de formulario válido. Intenta inspeccionar no HTML en seu lugar." }, "copyCustomFieldNameNotUnique": { - "message": "No unique identifier found." + "message": "Non se atopou ningún identificador único." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "message": "$ORGANIZATION$ 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", @@ -2821,31 +2757,31 @@ } }, "leaveOrganization": { - "message": "Leave organization" + "message": "Deixar a organización" }, "removeMasterPassword": { - "message": "Remove master password" + "message": "Eliminar contrasinal mestre" }, "removedMasterPassword": { - "message": "Master password removed" + "message": "Contrasinal mestre eliminado" }, "leaveOrganizationConfirmation": { - "message": "Are you sure you want to leave this organization?" + "message": "Estás seguro de que queres deixar esta organización?" }, "leftOrganization": { - "message": "You have left the organization." + "message": "Deixache a organización." }, "toggleCharacterCount": { - "message": "Toggle character count" + "message": "Alternar contador de caracteres" }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "O temporizador da sesión rematou. Por favor, volve iniciar a sesión." }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "Exportando caixa forte individual" }, "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": "Só se exportarán as entradas persoais da caixa forte asociada a $EMAIL$. As entradas da Caixa forte da organización non se incluirán. Tampouco se incluirá ningún arquivo anexo ás entradas.", "placeholders": { "email": { "content": "$1", @@ -2854,10 +2790,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "Exportar Caixa forte da organización" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Só se exportará a Caixa forte asociada a $ORGANIZATION$. As entradas en Caixas fortes individuais ou doutras organizacións non se incluirán.", "placeholders": { "organization": { "content": "$1", @@ -2868,17 +2804,28 @@ "error": { "message": "Erro" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { - "message": "Generate username" + "message": "Xerar nome de usuario" }, "generateEmail": { - "message": "Generate email" + "message": "Xerar enderezo electrónico" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "O valor debe estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,21 +2838,38 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": "Usar $RECOMMENDED$ caracteres ou máis para xerar un contrasinal seguro.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": "Usar $RECOMMENDED$ palabras ou máis para xerar unha frase de contrasinal segura.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "Correo con alias", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "Usar as funcións de subdireccionamento do teu provedor de correo." }, "catchallEmail": { - "message": "Catch-all email" + "message": "Correo \"catch-all\"" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "Usar a caixa de entrada \"catch-all\" configurada para o teu dominio." }, "random": { "message": "Aleatorio" @@ -2916,31 +2880,25 @@ "websiteName": { "message": "Nome do sitio web" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { - "message": "Service" + "message": "Servizo" }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Alias de correo redirixido" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Xerar un alias de correo cun servizo de redirecionado externo." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Dominio de correo", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Seleccionar un dominio compatible co servizo seleccionado", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "Erro de $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2954,11 +2912,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Xerado por Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Web: $WEBSITE$. Xerada por Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2968,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Token da API de $SERVICENAME$ inválido", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2978,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Token da API de $SERVICENAME$ inválido: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2992,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Non foi posible obter o ID de correo enmascarado de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3002,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Dominio de $SERVICENAME$ inválido.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3012,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Url de $SERVICENAME$ inválido.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3022,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Ocorreu un erro de $SERVICENAME$ descoñecido.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3032,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Redireccionador descoñecido: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3052,19 +3010,19 @@ "message": "Clave da API" }, "ssoKeyConnectorError": { - "message": "Key connector error: make sure key connector is available and working correctly." + "message": "Erro de conector de clave: asegúrate de que o conector de clave está dispoñible e funcionando correctamente." }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "Requírese plan Prémium" }, "organizationIsDisabled": { - "message": "Organization suspended." + "message": "Organización suspendida." }, "disabledOrganizationFilterError": { - "message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "As entradas de organizacións suspendidas son inaccesibles. Contacta co propietario da organización para asistencia." }, "loggingInTo": { - "message": "Logging in to $DOMAIN$", + "message": "Iniciando sesión en $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3073,25 +3031,25 @@ } }, "settingsEdited": { - "message": "Settings have been edited" + "message": "Os axustes foron modificados" }, "environmentEditedClick": { - "message": "Click here" + "message": "Preme aquí" }, "environmentEditedReset": { - "message": "to reset to pre-configured settings" + "message": "para volver á configuración por defecto" }, "serverVersion": { - "message": "Server version" + "message": "Versión do Servidor" }, "selfHostedServer": { "message": "autoaloxado" }, "thirdParty": { - "message": "Third-party" + "message": "De terceiros" }, "thirdPartyServerMessage": { - "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", + "message": "Conectado a un servidor dun terceiro, $SERVERNAME$. Por favor verifica bugs empregando o servidor oficial, ou comunícallos ó servidor da terceira parte.", "placeholders": { "servername": { "content": "$1", @@ -3100,7 +3058,7 @@ } }, "lastSeenOn": { - "message": "last seen on: $DATE$", + "message": "Visto por última vez o: $DATE$", "placeholders": { "date": { "content": "$1", @@ -3109,67 +3067,82 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Iniciar sesión co contrasinal mestre" }, "loggingInAs": { - "message": "Logging in as" + "message": "Iniciando sesión como" }, "notYou": { - "message": "Not you?" + "message": "Non es ti?" }, "newAroundHere": { - "message": "New around here?" + "message": "Novo por aquí?" }, "rememberEmail": { - "message": "Remember email" + "message": "Lembrar correo" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Iniciar sesión cun dispositivo" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "O inicio de sesión con dispositivos debe estar activado nos axustes da app de Bitwarden. Precisas doutro método?" }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "Frase de pegada dixital" }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device." + "message": "Por favor asegúrate de que a caixa forte está desbloqueada e a frase de pegada dixital coincide ca do outro dispositivo." }, "resendNotification": { - "message": "Resend notification" + "message": "Volver enviar notificación" + }, + "viewAllLogInOptions": { + "message": "Ver todas as opcións de inicio de sesión" }, - "viewAllLoginOptions": { - "message": "View all log in options" + "viewAllLoginOptionsV1": { + "message": "Ver todas as opcións de inicio de sesión" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "Enviouse unha notificación ó teu dispositivo." + }, + "aNotificationWasSentToYourDevice": { + "message": "Enviouse unha notificación ó teu dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Por favor asegúrate de que a sesión está aberta e a frase de pegada dixital coincide ca do outro dispositivo" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Serás notificado unha vez se aprobe a solicitude" + }, + "needAnotherOptionV1": { + "message": "Precisas doutro método?" }, "loginInitiated": { - "message": "Login initiated" + "message": "Inicio de sesión comezado" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "Contrasinal mestre filtrado" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "Contrasinal atopado nunha filtración de datos. Emprega un contrasinal único para a túa conta. Seguro que queres utilizar un contrasinal filtrado?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "Contrasinal mestre filtrado e feble" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "Contrasinal feble e atopado nunha filtración de datos. Emprega un contrasinal forte e único para a túa conta. Seguro que queres utilizar este contrasinal?" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "Comprobar este contrasinal en filtracións de datos coñecidas" }, "important": { - "message": "Important:" + "message": "Importante:" }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "O contrasinal mestre non pode ser recuperado se o esqueces!" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "Mínimo $LENGTH$ caracteres", "placeholders": { "length": { "content": "$1", @@ -3178,13 +3151,13 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Your organization policies have turned on autofill on page load." + "message": "As directivas da organización activaron o autoenchido ó cargar a páxina." }, "howToAutofill": { - "message": "How to autofill" + "message": "Como autoencher" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Selecciona unha entrada desta pantalla, emprega o atallo $COMMAND$, ou explora outras opcións en axustes.", "placeholders": { "command": { "content": "$1", @@ -3193,31 +3166,31 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Selecciona unha entrada desta pantalla ou explora outras opcións en axustes." }, "gotIt": { - "message": "Got it" + "message": "Entendido" }, "autofillSettings": { - "message": "Autofill settings" + "message": "Axustes de autoenchido" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "Atallo de autoenchido" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "Mudar atallo" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Xestionar atallos" }, "autofillShortcut": { - "message": "Autofill keyboard shortcut" + "message": "Atallo de teclado de autoenchido" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "O atallo de autoenchido de credenciais non está configurado. Cámbiao nos axustes do navegador." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "O atallo de autoenchido de credenciais é $COMMAND$. Xestiona os atallos nos axustes do navegador.", "placeholders": { "command": { "content": "$1", @@ -3226,7 +3199,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Default autofill shortcut: $COMMAND$.", + "message": "Atallo de autoenchido por defecto: $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -3235,56 +3208,65 @@ } }, "opensInANewWindow": { - "message": "Opens in a new window" + "message": "Abrir nunha ventá nova" + }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Lembrar este dispositivo para futuros inicios de sesión imperceptibles" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Aprobación de dispositivo requirida. Selecciona unha das seguintes opcións:" + }, + "deviceApprovalRequiredV2": { + "message": "Aprobación de dispositivo requirida" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecciona unha das seguintes opcións de aprobado" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Lembrar este dispositivo" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Desmarcar se se emprega un dispositivo público" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Aprobar dende o teu outro dispositivo" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Solicitar aprobación do administrador" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Aprobar con contrasinal mestre" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Identificador SSO da organización requirido." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Creando conta en" }, "checkYourEmail": { - "message": "Check your email" + "message": "Revisa o teu correo" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Segue o enlace no correo enviado a" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "e continúa ca creación da conta." }, "noEmail": { - "message": "No email?" + "message": "Sen correo?" }, "goBack": { - "message": "Go back" + "message": "Volver atrás" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "para modificar o teu enderezo electrónico." }, "eu": { "message": "UE", "description": "European Union" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Acceso denegado. Non tes permiso para ver esta páxina." }, "general": { "message": "Xeral" @@ -3293,48 +3275,51 @@ "message": "Amosar" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Conta creada con éxito!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Aprobación do administrador solicitada" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "A solicitude foi enviada ó teu administrador." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Serás notificado cando se aprobe." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Problemas ao iniciar sesión?" }, "loginApproved": { - "message": "Login approved" + "message": "Inicio de sesión aprobado" }, "userEmailMissing": { - "message": "User email missing" + "message": "Falta o correo electrónico" + }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Correo electrónico usuario activo non atopado. Cerrando a sesión." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Dispositivo de confianza" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "Sen Sends activos", "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.", + "message": "Usar send para compartir información cifrada con quen queiras.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "Requírese algunha entrada." }, "required": { - "message": "required" + "message": "requirido" }, "search": { - "message": "Search" + "message": "Busca" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "A entrada debe ser de polo menos $COUNT$ caracteres.", "placeholders": { "count": { "content": "$1", @@ -3343,7 +3328,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "A entrada debe ser de máximo $COUNT$ caracteres.", "placeholders": { "count": { "content": "$1", @@ -3352,7 +3337,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Os seguintes caracteres non están permitidos: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3361,7 +3346,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "O valor de entrada debe ser de mínimo $MIN$.", "placeholders": { "min": { "content": "$1", @@ -3370,7 +3355,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "O valor de entrada nun debe exceder $MAX$.", "placeholders": { "max": { "content": "$1", @@ -3379,17 +3364,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 ou máis correos son inválidos" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "A entrada non debe conter só espazos en branco.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "A entrada non é un enderezo de correo." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ campo(s) máis arriba precisan revisión.", "placeholders": { "count": { "content": "$1", @@ -3398,10 +3383,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "Un campo precisa revisión." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ campos precisan revisión.", "placeholders": { "count": { "content": "$1", @@ -3410,22 +3395,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Seleccionar --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Escribir para filtrar --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Cargando opcións..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Non se atoparon entradas" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Quitar seleccións" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ $QUANTITY$ máis", "placeholders": { "quantity": { "content": "$1", @@ -3434,168 +3419,176 @@ } }, "submenu": { - "message": "Submenu" + "message": "Submenú" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "Colapsar/Expandir", "description": "Toggling an expand/collapse state." }, "filelessImport": { - "message": "Import your data to Bitwarden?", + "message": "Importar os teus datos a Bitwarden?", "description": "Default notification title for triggering a fileless import." }, "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", + "message": "Protexer os teus datos de LastPass e importar a Bitwarden?", "description": "LastPass specific notification title for triggering a fileless import." }, "lpCancelFilelessImport": { - "message": "Save as unencrypted file", + "message": "Gardar como arquivo sen cifrar", "description": "LastPass specific notification button text for cancelling a fileless import." }, "startFilelessImport": { - "message": "Import to Bitwarden", + "message": "Importar a Bitwarden", "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importing...", + "message": "Importando...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "Data successfully imported!", + "message": "Datos importados con éxito!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Erro importando. Comproba a consola para máis detalle.", "description": "Notification message for when an import has failed." }, "importNetworkError": { - "message": "Network error encountered during import.", + "message": "Aconteceu un erro de rede durante a importación.", "description": "Notification message for when an import has failed due to a network error." }, "aliasDomain": { - "message": "Alias domain" + "message": "Alias do dominio" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be autofilled on page load. Autofill on page load turned off.", + "message": "As entradas que requiran volver a inserir o contrasinal mestre non poden ser autoenchidas ó cargar a páxina.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "Axuste de autoenchido ó cargar a páxina por defecto.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "Desactiva volver a requirir o contrasinal mestre para modificar este campo", "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": "Activar/desactivar navegación lateral" }, "skipToContent": { - "message": "Skip to content" + "message": "Ir ó contido" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "Botón do menú de autoenchido", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "Des/Activar o menú de autoenchido", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "Menú de autoenchido", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "Abre a caixa forte para ver as credenciais coincidentes", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "Abre a caixa forte para ver as suxestións de autoenchido", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "Abrir caixa forte", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "Abrir caixa forte nunha nova ventá", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Código de verificación basado en tempo de un uso", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Tempo para que o TOTP actual venza", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "Encher credenciais para", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "Nome de usuario parcial", "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": { - "message": "No items to show", + "message": "Sen entradas que amosar", "description": "Text to show in overlay if there are no matching items" }, "newItem": { - "message": "New item", + "message": "Nova entrada", "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Add new vault item", + "message": "Engadir unha nova entrada", "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "Nova credencial", "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": "Engadir unha nova credencial nunha nova ventá", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nova tarxeta", "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": "Engadir unha nova tarxeta nunha nova ventá", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "Nova identidade", "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": "Engadir unha nova identidade nunha nova ventá", "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": "Menú de autoenchido dispoñible. Pulsa a tecla de frecha abaixo para seleccionar.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "Activar" }, "ignore": { - "message": "Ignore" + "message": "Ignorar" }, "importData": { - "message": "Import data", + "message": "Importar datos", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Erro ó importar" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "Houbo un problema cos datos que intentas importar. Por favor, corrixe os erros listados a continuación e volve intentalo." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "Corrixe os erros listados a continuación e volve intentalo." }, "description": { - "message": "Description" + "message": "Descrición" }, "importSuccess": { - "message": "Data successfully imported" + "message": "Datos importados con éxito" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Importáronse $AMOUNT$ entradas.", "placeholders": { "amount": { "content": "$1", @@ -3604,46 +3597,46 @@ } }, "tryAgain": { - "message": "Try again" + "message": "Tentar de novo" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "Verificación requirida para esta acción. Crea un PIN para continuar." }, "setPin": { - "message": "Set PIN" + "message": "Crear PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Verificar con biometría" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Agardando confirmación" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Non se puido completar a biometría." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "Precisas dun método alternativo?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Usar contrasinal mestre" }, "usePin": { - "message": "Use PIN" + "message": "Usar PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Usar biometría" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Insire o código de verificación enviado ó teu correo." }, "resendCode": { - "message": "Resend code" + "message": "Volver enviar o código" }, "total": { "message": "Total" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "Estás importando datos a $ORGANIZATION$. Estes datos poden ser compartidos con membros da organización. Queres continuar?", "placeholders": { "organization": { "content": "$1", @@ -3652,49 +3645,49 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Erro conectando co servizo de Duo. Usa un método de verificación en 2 pasos alternativo ou contacta con Duo." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Executa Duo e segue os pasos para finalizar o inicio de sesión." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "A túa conta require a verificación en 2 pasos de Duo." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "Saca a extensión nunha ventá para continuar." }, "popoutExtension": { - "message": "Popout extension" + "message": "Sacar a extensión" }, "launchDuo": { - "message": "Launch Duo" + "message": "Executar Duo" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "Datos non estruturados correctamente. Por favor comproba o arquivo e téntao de novo." }, "importNothingError": { - "message": "Nothing was imported." + "message": "Nada foi importado." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "Erro descifrando o arquivo exportado. A túa clave de cifrado non coincide ca utilizada ó exportar os datos." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Contrasinal do arquivo inválido. Emprega o contrasinal que creaches cando se exportou o arquivo." }, "destination": { - "message": "Destination" + "message": "Destino" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "Aprende acerca das opcións de importado" }, "selectImportFolder": { - "message": "Select a folder" + "message": "Seleccionar un cartafol" }, "selectImportCollection": { - "message": "Select a collection" + "message": "Seleccionar unha colección" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "Selecciona esta opción se queres que o contido importado se mova a $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": { @@ -3704,25 +3697,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "O arquivo contén entradas sen asignar." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "Selecciona o formato do arquivo de importado" }, "selectImportFile": { - "message": "Select the import file" + "message": "Selecciona o arquivo de importado" }, "chooseFile": { - "message": "Choose File" + "message": "Seleccionar arquivo" }, "noFileChosen": { - "message": "No file chosen" + "message": "Ningún arquivo escollido" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "ou copia/pega o contido do arquivo de importado" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "Instrucións para $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3732,197 +3725,200 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Confirmar o importado da caixa forte" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Este arquivo está protexido. Insire o contrasinal para importar os datos." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Repetir contrasinal do arquivo" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Caixa forte exportada" }, "typePasskey": { - "message": "Passkey" + "message": "Clave de acceso" }, "accessing": { - "message": "Accessing" + "message": "Accedendo" + }, + "loggedInExclamation": { + "message": "Sesión iniciada!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "A Clave de acceso non se vai copiar" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "A Clave de acceso non se incluirá na entrada clonada. Queres continuar co duplicado?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { - "message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password." + "message": "Verificación requirida polo sitio inicial. Esta función aínda non está implementada para contas sen contrasinal mestre." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Iniciar sesión con Clave de acceso?" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "Xa existe unha Clave de acceso para esta aplicación." }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "Non se atoparon Claves de acceso para esta aplicación." }, "noMatchingPasskeyLogin": { - "message": "You do not have a matching login for this site." + "message": "Non tes Claves de acceso coincidentes para este sitio." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Non tes credenciais coincidentes para este sitio" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Busca ou garda a Clave de acceso como nova credencial" }, "confirm": { - "message": "Confirm" + "message": "Confirmar" }, "savePasskey": { - "message": "Save passkey" + "message": "Gardar Clave de acceso" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Gardar Clave de acceso como nova credencial" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Escolle unha credencial na que gardar esta Clave de acceso" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Escolle unha Clave de acceso ca que iniciar sesión" }, "passkeyItem": { - "message": "Passkey Item" + "message": "Entrada de Clave de acceso" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "Sobrescribir Clave de acceso?" }, "overwritePasskeyAlert": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "Esta entrada xa contén unha Clave de acceso. Seguro que queres sobreescribir a existente?" }, "featureNotSupported": { - "message": "Feature not yet supported" + "message": "Función aínda non implementada" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "A autenticación require unha Clave de acceso. Verifica a túa identidade para continuar." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "Autenticación multifactor cancelada" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "Non se atoparon datos de LastPass" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "Nome de usuario ou contrasinal incorrectos" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Contrasinal incorrecto" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Código incorrecto" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN incorrecto" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "Autenticación multifactor fallida" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "Incluír cartafois compartidos" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "Correo de LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "Importar a túa conta..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "Autenticación multifactor de LastPass requirida" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "Insire o código de un uso da túa app de autenticación" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "Aproba a petición de inicio de sesión na túa app de autenticación ou insire o código dun uso." }, "passcode": { - "message": "Passcode" + "message": "Código de acceso" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "Contrasinal mestre de LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "Autenticación de LastPass requirida" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "Agardando pola autenticación SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "Por favor, continúa empregando as credenciais da túa compañía." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "Ver instrucións detalladas no noso sitio de axuda en", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "Importar directamente de LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "Importar dun CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "Volver intentar ou buscar un correo de LastPass para verificar que es ti." }, "collection": { - "message": "Collection" + "message": "Colección" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Conecta a YubiKey asociada á túa conta de LastPass, e preme o seu botón." }, "switchAccount": { - "message": "Switch account" + "message": "Cambiar de conta" }, "switchAccounts": { - "message": "Switch accounts" + "message": "Cambiar as contas" }, "switchToAccount": { - "message": "Switch to account" + "message": "Cambiar a conta" }, "activeAccount": { - "message": "Active account" + "message": "Activar conta" }, "availableAccounts": { - "message": "Available accounts" + "message": "Contas dispoñibles" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "Límite de contas alcanzado. Cerra sesión nunha delas para engadir outra." }, "active": { - "message": "active" + "message": "activo/a" }, "locked": { - "message": "locked" + "message": "bloqueado" }, "unlocked": { - "message": "unlocked" + "message": "desbloqueado" }, "server": { - "message": "server" + "message": "servidor" }, "hostedAt": { - "message": "hosted at" + "message": "aloxado en" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Usa o teu dispositivo ou chave de hardware" }, "justOnce": { - "message": "Just once" + "message": "Só unha vez" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "Sempre para este sitio" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ engadido ós dominios excluídos.", "placeholders": { "domain": { "content": "$1", @@ -3931,103 +3927,103 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "Formatos comúns", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Ir ós axustes do navegador?", "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": "Ir ó Centro de Axuda?", "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": "Cambiar os axustes de autoenchido e xestión de contrasinais do 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": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "Podes ver e crear atallos da extensión nos axustes do teu navegador.", "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": "Cambiar os axustes de autoenchido e xestión de contrasinais do navegador.", "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": "Podes ver e crear atallos da extensión nos axustes do teu navegador.", "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": "Facer de Bitwarden o teu xestor de contrasinais por defecto?", "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": "Ignorar esta opción pode causar conflitos entre Bitwarden e o xestor de contrasinais do teu navegador.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Facer de Bitwarden o teu xestor de contrasinais por defecto", "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": "Non se puido facer de Bitwarden o xestor de contrasinais por defecto", "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": "Debes conceder ó navegador permisos de privacidade sobre Bitwarden para facelo o xestor de contrasinais por defecto.", "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": "Facer o predefinido", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Credenciais gardadas con éxito!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Contrasinal gardado!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Credenciais actualizadas con éxito!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Contrasinal actualizado!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Erro ó gardar as credenciais. Comproba a consola para máis detalle.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Éxito" }, "removePasskey": { - "message": "Remove passkey" + "message": "Eliminar Clave de acceso" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Clave de acceso eliminada" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Suxestións de autoenchido" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "Gardar unha credencial como suxestión para este sitio" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "A caixa forte está baleira" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Non hai entradas que coincidan ca túa busca" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Quitar filtros e tentar outro termo de busca" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "Copiar información - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4037,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "Copiar nota - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4047,7 +4043,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Máis opcións, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4057,7 +4053,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Máis opcións - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Ver entrada - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4077,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "Autoenchido - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4087,40 +4083,40 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "Non hai valores que copiar" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Vincular con coleccións" }, "copyEmail": { - "message": "Copy email" + "message": "Copiar correo" }, "copyPhone": { - "message": "Copy phone" + "message": "Copiar teléfono" }, "copyAddress": { - "message": "Copy address" + "message": "Copiar enderezo" }, "adminConsole": { - "message": "Admin Console" + "message": "Consola do administrador" }, "accountSecurity": { - "message": "Account security" + "message": "Seguridade da conta" }, "notifications": { - "message": "Notifications" + "message": "Notificacións" }, "appearance": { - "message": "Appearance" + "message": "Aparencia" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Erro ó vincular esta colección." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Erro ó vincular este cartafol." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Ver entradas en $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4130,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Volver a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4140,10 +4136,10 @@ } }, "new": { - "message": "New" + "message": "Novo" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Eliminar $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4153,16 +4149,16 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Entradas sen carpeta" }, "itemDetails": { - "message": "Item details" + "message": "Detalles do entrada" }, "itemName": { - "message": "Item name" + "message": "Nome da entrada" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Non podes eliminar coleccións con permisos de Só lectura: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -4171,47 +4167,47 @@ } }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "A organización está desactivada" }, "owner": { - "message": "Owner" + "message": "Propietario" }, "selfOwnershipLabel": { - "message": "You", + "message": "Ti", "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": "As entradas en organizacións suspendidas son inaccesibles. Contacta co propietario da organización para asistencia." }, "additionalInformation": { - "message": "Additional information" + "message": "Información adicional" }, "itemHistory": { - "message": "Item history" + "message": "Historial da entrada" }, "lastEdited": { - "message": "Last edited" + "message": "Modificado o" }, "ownerYou": { - "message": "Owner: You" + "message": "Propietario: Ti" }, "linked": { - "message": "Linked" + "message": "Vinculado" }, "copySuccessful": { - "message": "Copy Successful" + "message": "Copiado realizado" }, "upload": { - "message": "Upload" + "message": "Subir" }, "addAttachment": { - "message": "Add attachment" + "message": "Anexar arquivo" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "O tamaño máximo é de 500 MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "Eliminar anexos $NAME$", "placeholders": { "name": { "content": "$1", @@ -4220,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "Descargar $NAME$", "placeholders": { "name": { "content": "$1", @@ -4229,28 +4225,43 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Estás seguro de que queres eliminar permanentemente este anexo?" }, "premium": { - "message": "Premium" + "message": "Prémium" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "As organizacións gratuitas non poden empregar anexos" }, "filters": { - "message": "Filters" + "message": "Filtros" + }, + "filterVault": { + "message": "Filtros da caixa forte" + }, + "filterApplied": { + "message": "Un filtro en uso" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtros en uso", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } }, "personalDetails": { - "message": "Personal details" + "message": "Detalles persoais" }, "identification": { - "message": "Identification" + "message": "Identificación" }, "contactInfo": { - "message": "Contact info" + "message": "Información de contacto" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Descargar - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4259,23 +4270,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "o número de tarxeta remata en", "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": "Credenciais de inicio de sesión" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Clave de autenticación" }, "autofillOptions": { - "message": "Autofill options" + "message": "Opcións de autoenchido" }, "websiteUri": { - "message": "Website (URI)" + "message": "Dirección web (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Dirección 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": { @@ -4285,16 +4296,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Dirección web engadida" }, "addWebsite": { - "message": "Add website" + "message": "Engadir dirección web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Eliminar dirección web" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Predeterminado ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4304,7 +4315,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Mostrar detección de coincidencia $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4313,7 +4324,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Agochar detección de coincidencia $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4322,19 +4333,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Autoencher ó cargar a páxina?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Tarxeta vencida" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Se a renovas, actualiza a información da tarxeta" }, "cardDetails": { - "message": "Card details" + "message": "Detalles da tarxeta" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Detalles da $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -4343,43 +4354,43 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "Activar animacións" }, "showAnimations": { - "message": "Show animations" + "message": "Amosar animacións" }, "addAccount": { - "message": "Add account" + "message": "Engadir conta" }, "loading": { - "message": "Loading" + "message": "Cargando" }, "data": { - "message": "Data" + "message": "Datos" }, "passkeys": { - "message": "Passkeys", + "message": "Claves de acceso", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Contrasinais", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Iniciar sesión con Clave de acceso", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "Asignar" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "Só os membros da organización con acceso a estas coleccións poderán ver esta entrada." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Só os membros da organización con acceso a estas coleccións poderán ver estas entradas." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "Seleccionaches $TOTAL_COUNT$ entradas. Non vas poder modificar $READONLY_COUNT$ delas porque non tes permisos de edición.", "placeholders": { "total_count": { "content": "$1", @@ -4391,37 +4402,37 @@ } }, "addField": { - "message": "Add field" + "message": "Engadir un campo" }, "add": { - "message": "Add" + "message": "Engadir" }, "fieldType": { - "message": "Field type" + "message": "Tipo de campo" }, "fieldLabel": { - "message": "Field label" + "message": "Título do campo" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Emprega campos de texto para datos como preguntas de seguridade" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Emprega campos agochados para datos sensibles como contrasinais" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Emprega caixas de verificación cando queiras autoencher as dun formulario, como \"Lembrar correo\"" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "Emprega un campo vinculado cando teñas problemas coa autoenchido dalgunha web." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "Insire o ID HTML, nome, aria-label ou exemplo foi campo." }, "editField": { - "message": "Edit field" + "message": "Modificar campo" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Modificar $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Eliminar $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ engadido", "placeholders": { "label": { "content": "$1", @@ -4448,7 +4459,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "Recolocar $LABEL$. Emprega as frechas do teclado.", "placeholders": { "label": { "content": "$1", @@ -4457,7 +4468,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "Subiuse $LABEL$ á posición $INDEX$ de $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4474,13 +4485,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Seleccionar coleccións a vincular" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "Unha entrada será irreversiblemente transferida á organización. Xa non serás o seu propietario." }, "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$ entradas serán irreversiblemente transferidas á organización. Xa non serás o seu propietario.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4489,7 +4500,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "Unha entrada será irreversiblemente transferida a $ORG$. Xa non serás o seu propietario.", "placeholders": { "org": { "content": "$1", @@ -4498,7 +4509,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ entradas serán irreversiblemente transferidas a $ORG$. Xa non serás o seu propietario.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4511,13 +4522,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "Coleccións vinculadas" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "Non tes nada seleccionado." }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "Entradas seleccionadas transferidas a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4537,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "Entradas transferidas a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4535,7 +4546,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "Entrada transferida a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4544,7 +4555,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "Baixouse $LABEL$ á posición $INDEX$ de $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4561,231 +4572,336 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "Ubicación da entrada" }, "fileSend": { - "message": "File Send" + "message": "Arquivo Send" }, "fileSends": { - "message": "File Sends" + "message": "Arquivos Send" }, "textSend": { - "message": "Text Send" + "message": "Texto Send" }, "textSends": { - "message": "Text Sends" + "message": "Textos Send" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden ten un novo look!" }, "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "É máis fácil e intuitivo que nunca autoencher e buscar dende a caixa forte. Bota un ollo!" }, "accountActions": { - "message": "Account actions" + "message": "Accións da conta" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "Amosar o número de suxestións de credenciais na icona da extensión" + }, + "showQuickCopyActions": { + "message": "Amosar accións rápidas de copiado na caixa forte" }, "systemDefault": { - "message": "System default" + "message": "Predefinido do sistema" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "Directivas da empresa foron aplicadas a esta opción" + }, + "sshPrivateKey": { + "message": "Clave privada" + }, + "sshPublicKey": { + "message": "Clave pública" + }, + "sshFingerprint": { + "message": "Pegada dixital" + }, + "sshKeyAlgorithm": { + "message": "Tipo de clave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "Reintentar" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "O tempo mínimo personalizado é de 1 minuto." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Hai dispoñibles contidos adicionais" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "Arquivo gardado no dispositivo. Xestiónao dende as descargas do dispositivo." }, "showCharacterCount": { - "message": "Show character count" + "message": "Amosar contador de caracteres" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Ocultar contador de caracteres" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Entradas no lixo" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Sen entradas no lixo" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "As entradas que elimines aparecerán aquí serán permanente eliminadas despois de 30 días" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "As entradas que estean no lixo máis de 30 días serán automaticamente eliminadas" }, "restore": { - "message": "Restore" + "message": "Restaurar" }, "deleteForever": { - "message": "Delete forever" + "message": "Eliminar permanentemente" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Non tes permiso para modificar esta entrada" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." }, "authenticating": { - "message": "Authenticating" + "message": "Autenticando" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Encher co contrasinal xerado", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Contrasinal xerado de novo", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Gardar credenciais en Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Espazo", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Til", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Plica", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Exclamación", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Arroba", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Símbolo de almohadilla", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Símbolo do dólar", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Símbolo de porcentaxe", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Símbolo circunflexo", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Símbolo E comercial", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Símbolo Asterisco", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Símbolo abrir paréntese", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Símbolo pechar paréntese", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Símbolo guión baixo", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Símbolo guión", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Símbolo máis", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Símbolo igual", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Símbolo abrir chave", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Símbolo pechar chave", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Símbolo abrir corchete", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Símbolo pechar corchete", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Símbolo de liña vertical (pipe)", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Símbolo de barra invertida", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Símbolo de dous puntos", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Símbolo de punto e coma", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Símbolo de dobre comilla", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Símbolo de comilla simple", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Símbolo de menor que", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Símbolo de maior que", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Símbolo de coma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Símbolo de punto", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Símbolo de interrogación", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Símbolo de barra inclinada", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Minúsculas" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Maiúsculas" }, "generatedPassword": { - "message": "Generated password" + "message": "Contrasinal xerado" + }, + "compactMode": { + "message": "Modo compacto" + }, + "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" + }, + "wide": { + "message": "Ancho" + }, + "extraWide": { + "message": "Moi ancho" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 0158ef20d67..1a3057ac291 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "מילוי פרטי זיהוי אוטומטית" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "צור סיסמה (העתק)" }, @@ -438,9 +454,6 @@ "length": { "message": "אורך" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "מינימום תוים מיוחדים" }, - "avoidAmbChar": { - "message": "המנע מאותיות ותוים דומים", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "דירוג הרחבה" }, - "rateExtensionDesc": { - "message": "אם נהנית מהתוכנה, בבקשה דרג את התוכנה וכתוב דירוג עם חוות דעת טובה!" - }, "browserNotSupportClipboard": { "message": "הדפדפן שלך לא תומך בהעתקה ללוח. אנא העתק בצורה ידנית." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "נקה לוח העתקות", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "אזהרה", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "אישור ייצוא כספת" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "שתף" - }, "movedItemToOrg": { "message": "$ITEMNAME$ הועבר ל- $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "הכנס את קוד האימות בן 6 הספרות מאפליקציית האימות שלך." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "הכנס את קוד האימות בן 6 הספרות שנשלח ל-$EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "אם זוהה טופס כניסה, בצע אוטומטית מילוי-אוטומטי כשהעמוד נטען." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "זהות" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "פתקים מאובטחים" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "נקה", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "שכפול" }, - "passwordGeneratorPolicyInEffect": { - "message": "מדיניות ארגונית אחת או יותר משפיעה על הגדרות המחולל שלך." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "הקובץ שברצונך לשלוח." - }, "deletionDate": { "message": "תאריך מחיקה" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "תאריך תפוגה" }, - "expirationDateDesc": { - "message": "במידה ויוגדר, הגישה ל Send זה תושבת בתאריך ובשעה שהוגדרו.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "יום אחד" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "מותאם אישית" }, - "maximumAccessCount": { - "message": "כמות גישות מרבית" - }, - "maximumAccessCountDesc": { - "message": "במידה ויוגדר, משתמשים לא יוכלו יותר לגשת ל Send זה לאחר שמספר הגישות המרבי יושג.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "שגיאה" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "סוג שם משתמש" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 24a4833e569..fd4a6612af4 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -20,16 +20,16 @@ "message": "Create Account" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "बिटवार्डन का परिचय" }, "logInWithPasskey": { "message": "Log in with passkey" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "सिंगल साइन-ऑन प्रयोग करें" }, "welcomeBack": { - "message": "Welcome back" + "message": "आपका पुन: स्वागत है!" }, "setAStrongPassword": { "message": "मजबूत पासवर्ड सेट करें" @@ -138,10 +138,10 @@ "message": "Copy Security Code" }, "copyName": { - "message": "Copy name" + "message": "नाम कॉपी करें" }, "copyCompany": { - "message": "Copy company" + "message": "कंपनी के नाम को कॉपी करें" }, "copySSN": { "message": "Copy Social Security number" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "स्वचालित पहचान विवरण" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate Password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "लंबाई" }, - "passwordMinLength": { - "message": "न्यूनतम पासवर्ड लंबाई" - }, "uppercase": { "message": "बड़े अक्षर (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum Special" }, - "avoidAmbChar": { - "message": "Avoid Ambiguous Characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the Extension" }, - "rateExtensionDesc": { - "message": "कृपया एक अच्छी समीक्षा के साथ हमारी मदत करने पर विचार करें!" - }, "browserNotSupportClipboard": { "message": "आपका वेब ब्राउज़र आसान क्लिपबोर्ड कॉपीिंग का समर्थन नहीं करता है। इसके बजाय इसे मैन्युअल रूप से कॉपी करें।" }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "आसान ऑटो-फिल के लिए टैब पेज पर कार्ड आइटम सूचीबद्ध करें।" }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "क्लिपबोर्ड खाली करें", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "चेतावनी", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "वॉल्ट निर्यात की पुष्टि करें" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "संगठन में ले जाएँ" }, - "share": { - "message": "शेयर करें" - }, "movedItemToOrg": { "message": "$ITEMNAME$ $ORGNAME$ गया ", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "अपने ऑथेंटिकेटर ऐप से 6 डिजिट वेरिफिकेशन कोड डालें।" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "यदि लॉगिन फॉर्म का पता चलता है, तो वेब पेज लोड होने पर स्वचालित रूप से ऑटो-फिल करें।" }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "पहचान" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "नया $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure Notes" }, + "sshKeys": { + "message": "SSH कुँजी" + }, "clear": { "message": "खाली करें", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "क्लोन" }, - "passwordGeneratorPolicyInEffect": { - "message": "एक या एक से अधिक संगठन नीतियां आपकी जनरेटर सेटिंग को प्रभावित कर रही हैं।" - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "बहिष्कृत डोमेन" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Sends मे खोजे", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send जोड़ें", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "शब्द" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "मैक्स एक्सेस काउंट पहुंच गया है", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": " गतावधिक" }, - "pendingDeletion": { - "message": "हटाना लंबित" - }, "passwordProtected": { "message": "पासवर्ड सुरक्षित है" }, @@ -2456,24 +2462,9 @@ "message": "एडिट Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "यह किस प्रकार का सेंड है?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "इस सेंड का वर्णन करने के लिए एक दोस्ताना नाम।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "वह फाइल जो आप सेंड करना चाहते हैं।" - }, "deletionDate": { "message": "हटाने की तारीख" }, - "deletionDateDesc": { - "message": " यह सेंड निर्धारित तिथि और समय पर स्थायी रूप से हटा दिया जाएगा।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "समाप्ति तिथि" }, - "expirationDateDesc": { - "message": "यदि सेट किया जाता है, तो यह सेंड निर्दिष्ट तिथि और समय पर समाप्त हो जाएगा।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 दिन" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "कस्टम" }, - "maximumAccessCount": { - "message": "अधिकतम एक्सेस काउंट" - }, - "maximumAccessCountDesc": { - "message": "यदि सेट किया जाता है, तो अधिकतम एक्सेस काउंट तक पहुंचने के बाद उपयोगकर्ता अब इस सेंड को एक्सेस नहीं कर पाएंगे।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "वैकल्पिक रूप से उपयोगकर्ताओं को इस सेंड तक पहुंचने के लिए पासवर्ड की आवश्यकता होगी।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "इस सेंड के बारे में निजी नोट्स।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "इस सेंड को अक्षम करें ताकि कोई भी इसे एक्सेस न कर सके।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "सेव पर क्लिपबोर्ड पर इस सेंड के लिंक को कॉपी करें।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "वह टेक्स्ट जो आप सेंड करना चाहते हैं।" - }, - "sendHideText": { - "message": "इस सेंड के टेक्स्ट को डिफ़ॉल्ट रूप से छिपाएं।", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "वर्तमान एक्सेस काउंट" - }, "createSend": { "message": "नया सेंड बनाएं", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "शुरू करने से पहले" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "कैलेंडर शैली तिथि बीनने वाले का उपयोग करने के लिए", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "यहां क्लिक करें", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "अपनी विंडो पॉप आउट करने के लिए यहां क्लिक करें।", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "प्रदान की गई समाप्ति तिथि मान्य नहीं है।" }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "आपके विलोपन और समाप्ति तिथियों को सहेजने में एक त्रुटि थी।" }, - "hideEmail": { - "message": "प्राप्तकर्ताओं से मेरा ईमेल पता छिपाएं।" - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "एक या एक से अधिक संगठन नीतियां आपके सेंड विकल्पों को प्रभावित कर रही हैं।" - }, "passwordPrompt": { "message": "मास्टर पासवर्ड रि-प्रॉम्प्ट" }, @@ -2868,8 +2804,19 @@ "error": { "message": "एरर" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "उपयोगकर्ता नाम बनाएँ" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "वेबसाइट का नाम" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "ऐक्सेसिंग" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "फ़िल्टर" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "इस सेटिंग पर एंटरप्राइज़ नीति आवश्यकताएँ लागू की गई हैं" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "पुन: प्रयास करें" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 4f206edd301..331bc109309 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -20,16 +20,16 @@ "message": "Stvori račun" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novi u Bitwardenu?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prijava pristupnim ključem" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Jedinstvena prijava (SSO)" }, "welcomeBack": { - "message": "Welcome back" + "message": "Dobro došli natrag" }, "setAStrongPassword": { "message": "Postavi jaku lozinku" @@ -84,7 +84,7 @@ "message": "Pridruži se organizaciji" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Pidruži se $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +120,7 @@ "message": "Kopiraj lozinku" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Kopiraj fraznu lozinku" }, "copyNote": { "message": "Kopiraj bilješku" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopiraj OIB" }, + "copyPrivateKey": { + "message": "Kopiraj privatni ključ" + }, + "copyPublicKey": { + "message": "Kopiraj javni ključ" + }, + "copyFingerprint": { + "message": "Kopiraj otisak prsta" + }, "copyCustomField": { "message": "Kopiraj $FIELD$", "placeholders": { @@ -168,7 +177,7 @@ "message": "Kopiraj bilješke" }, "fill": { - "message": "Fill", + "message": "Ispuni", "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Auto-ispuna identiteta" }, + "fillVerificationCode": { + "message": "Ispuni kôd za provjeru" + }, + "fillVerificationCodeAria": { + "message": "Ispuni kôd za provjeru", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generiraj lozinku (i kopiraj)" }, @@ -268,7 +284,7 @@ "message": "Nastavi na web aplikaciju?" }, "continueToWebAppDesc": { - "message": "Pronađi viđe značajki svojeg Bitwarden računa u web aplikaciji." + "message": "Pronađi više značajki svojeg Bitwarden računa u web aplikaciji." }, "continueToHelpCenter": { "message": "Nastavi u centar za pomoć?" @@ -333,10 +349,10 @@ "message": "Stvori laka i sigurna iskustva prijave bez tradicionalnih lozinki uz Passwordless.dev. Saznaj više na web stranici bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Besplatan obiteljski Bitwarden" + "message": "Besplatni Bitwarden Families" }, "freeBitwardenFamiliesPageDesc": { - "message": "Ispunjavaš uvjete za besplatni obiteljski Bitwarden. Iskoristi ovu ponudu u web aplikaciji već danas." + "message": "Ispunjavaš uvjete za besplatni Bitwarden Families. Iskoristi ovu ponudu u web aplikaciji već danas." }, "version": { "message": "Verzija" @@ -427,7 +443,7 @@ "message": "Generiraj lozinku" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generiraj frazu lozinke" }, "regeneratePassword": { "message": "Ponovno generiraj lozinku" @@ -438,9 +454,6 @@ "length": { "message": "Duljina" }, - "passwordMinLength": { - "message": "Minimalna duljina lozinke" - }, "uppercase": { "message": "Velika slova (A - Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Najmanje posebnih" }, - "avoidAmbChar": { - "message": "Izbjegavaj dvosmislene znakove", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Izbjegavaj dvosmislene znakove", "description": "Label for the avoid ambiguous characters checkbox." @@ -591,7 +600,7 @@ "message": "Pokreni web stranicu" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Otvori stranicu $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "Ocijeni proširenje" }, - "rateExtensionDesc": { - "message": "Razmotri da nam pomogneš dobrom recenzijom!" - }, "browserNotSupportClipboard": { "message": "Web preglednik ne podržava jednostavno kopiranje međuspremnika. Umjesto toga ručno kopirajte." }, @@ -788,7 +794,7 @@ "message": "Poslali smo e-poštu s podsjetnikom glavne lozinke." }, "verificationCodeRequired": { - "message": "Potvrdni kôd je obavezan." + "message": "Kôd za provjeru je obavezan." }, "webauthnCancelOrTimeout": { "message": "Autentifikacija je otkazana ili je trajala predugo. Molimo pokušaj ponovno." @@ -846,7 +852,7 @@ "message": "Prijavi se" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Prijavi se u Bitwarden" }, "restartRegistration": { "message": "Ponovno pokreni registraciju" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Prikazuj identitete za jednostavnu auto-ispunu." }, + "clickToAutofillOnVault": { + "message": "Klikni stavke za auto-ispunu na prikazu trezora" + }, "clearClipboard": { "message": "Očisti međuspremnik", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "UPOZORENJE", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Upozorenje", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Potvrdi izvoz trezora" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Premjesti u organizaciju" }, - "share": { - "message": "Podijeli" - }, "movedItemToOrg": { "message": "$ITEMNAME$ premješteno u $ORGNAME$", "placeholders": { @@ -1292,7 +1302,7 @@ "message": "Automatski kopiraj TOTP" }, "disableAutoTotpCopyDesc": { - "message": "Ako za prijavu postoji autentifikatorski ključ, kopiraj TOTP kontrolni kôd u međuspremnik nakon auto-ispune prijave." + "message": "Ako za prijavu postoji autentifikatorski ključ, kopiraj TOTP kôd za provjeru u međuspremnik nakon auto-ispune prijave." }, "enableAutoBiometricsPrompt": { "message": "Traži biometrijsku autentifikaciju pri pokretanju" @@ -1304,10 +1314,16 @@ "message": "Za korištenje ove značajke potrebno je Premium članstvo." }, "enterVerificationCodeApp": { - "message": "Unesi 6-znamenkasti kontrolni kôd iz autentifikatorske aplikacije." + "message": "Unesi 6-znamenkasti kôd za provjeru iz autentifikatorske aplikacije." + }, + "authenticationTimeout": { + "message": "Istek vremena za autentifikaciju" + }, + "authenticationSessionTimedOut": { + "message": "Sesija za autentifikaciju je istekla. Ponovi proces prijave." }, "enterVerificationCodeEmail": { - "message": "Unesi 6-znamenkasti kontrolni kôd poslan e-poštom na $EMAIL$.", + "message": "Unesi 6-znamenkasti kôd za provjeru poslan e-poštom na $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1424,7 +1440,7 @@ "message": "URL poslužitelja" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL vlastitog poslužitelja", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1456,10 +1472,10 @@ "message": "Prikaži prijedloge auto-ispune na poljima obrazaca" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Prikaži identitete kao prijedloge" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Prikaži platne kartice kao prijedloge" }, "showInlineMenuOnIconSelectionLabel": { "message": "Prikaži prijedloge kada je odabrana ikona" @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Nakon učitavanja web stranice, ako je otkriven obrazac za prijavu, auto-ispuni." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Upozorenje:$CLOSETAG$ Ugrožene ili nepouzdane web stranice mogu iskoristiti auto-ispunu prilikom učitavanja stranice.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Ugrožene ili nepouzdane web stranice mogu iskoristiti auto-ispunu prilikom učitavanja stranice." }, @@ -1591,7 +1594,7 @@ "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "Ako klikneš izvan iskočnog prozora, za provjeru kontrolnog kôda iz e-pošte, on će se zatvoriti. Želiš li ovaj iskočni prozor otvoriti u novom prozoru kako se ne bi zatvorio?" + "message": "Ako klikneš izvan iskočnog prozora, za provjeru kôda za provjeru iz e-pošte, on će se zatvoriti. Želiš li ovaj iskočni prozor otvoriti u novom prozoru kako se ne bi zatvorio?" }, "popupU2fCloseMessage": { "message": "Ovaj preglednik ne može obraditi U2F zahtjeve u ovom iskočnom prozoru. Želiš li otvoriti ovaj iskočni prozor u novom prozoru za prijavu putem U2F?" @@ -1666,7 +1669,7 @@ "message": "prosinac" }, "securityCode": { - "message": "Kontrolni broj" + "message": "Sigurnosni kôd" }, "ex": { "message": "npr." @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitet" }, + "typeSshKey": { + "message": "SSH ključ" + }, "newItemHeader": { "message": "Novi $TYPE$", "placeholders": { @@ -1795,13 +1801,13 @@ "message": "Povijest" }, "generatorHistory": { - "message": "Generator history" + "message": "Povijest generatora" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Očisti povijest generatora" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Cijela povijest generatora biti će trajno izbirsana. Sigurno želiš nastaviti?" }, "back": { "message": "Natrag" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Sigurne bilješke" }, + "sshKeys": { + "message": "SSH ključevi" + }, "clear": { "message": "Očisti", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "Očisti povijest" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ništa za prikazati" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Ništa nije generirano" }, "remove": { "message": "Ukloni" @@ -2031,9 +2040,6 @@ "clone": { "message": "Kloniraj" }, - "passwordGeneratorPolicyInEffect": { - "message": "Jedno ili više pravila organizacije utječe na postavke generatora." - }, "passwordGenerator": { "message": "Generator lozinki" }, @@ -2318,6 +2324,9 @@ "message": "Domene", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Izuzete domene" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden neće nuditi spremanje podataka za prijavu za ove domene za sve prijavljene račune. Moraš osvježiti stranicu kako bi promjene stupile na snagu." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web stranica $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Spremljene promjene izuzete domene" }, @@ -2373,14 +2394,6 @@ "message": "Detalji Senda", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Pretraži Sendove", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Dodaj Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Zadano sakrij tekst" }, - "maxAccessCountReached": { - "message": "Dostignut najveći broj pristupanja", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Isteklo" }, - "pendingDeletion": { - "message": "Čeka brisanje" - }, "passwordProtected": { "message": "Zaštićeno lozinkom" }, @@ -2456,24 +2462,9 @@ "message": "Uredi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Koja je ovo vrsta Senda?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Nadimak za ovaj Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Datoteka koju želiš poslati" - }, "deletionDate": { "message": "Obriši za" }, - "deletionDateDesc": { - "message": "Send će nakon navedenog vremena biti trajno izbrisan.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send će na ovaj datum biti trajno izbrisan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Vremenski ograničeni pristup" }, - "expirationDateDesc": { - "message": "Pristup ovom Sendu neće biti moguć nakon navednog roka.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dan" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Prilagođeno" }, - "maximumAccessCount": { - "message": "Ograničeni broj pristupanja" - }, - "maximumAccessCountDesc": { - "message": "Ako je određen, ovom Sendu će se moći pristupiti samo ograničeni broj puta.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Neobavezno zahtijevaj korisnika lozinku za pristup ovom Sendu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privatne bilješke o Sendu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Onemogući ovaj Send da mu nitko ne može pristupiti.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopiraj vezu na Send nakon spremanja", + "message": "Dodaj opcionalnu lozinku za primatelje ovog Senda.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTextDesc": { - "message": "Tekst kojeg želiš poslati" - }, - "sendHideText": { - "message": "Sakrij tekst ovog Senda.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Trenutni broj pristupanja" - }, "createSend": { "message": "Stvori novi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Prije početka" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Biranje datuma na kalendaru", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klikni ovjde", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "za iskočni prozor", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Navedeni rok isteka nije valjan." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Došlo je do greške kod spremanja vaših datuma isteka i brisanja." }, - "hideEmail": { - "message": "Sakrij moju adresu e-pošte od primatelja." - }, "hideYourEmail": { "message": "Autentifikacija" }, - "sendOptionsPolicyInEffect": { - "message": "Jedno ili više pravila organizacije utječe na postavke Senda." - }, "passwordPrompt": { "message": "Ponovno zatraži glavnu lozinku" }, @@ -2710,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "od $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2868,17 +2804,28 @@ "error": { "message": "Pogreška" }, - "regenerateUsername": { - "message": "Ponovno generiraj korisničko ime" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generiraj korisničko ime" }, "generateEmail": { - "message": "Generate email" + "message": "Generiraj e-poštu" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Vrijednost mora biti u rasponu $MIN$ - $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tip korisničkog imena" + "passwordLengthRecommendationHint": { + "message": " Koristi $RECOMMENDED$ i više znakova za generiranje jake lozinke.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Koristi $RECOMMENDED$ i više riječi za generiranje jake frazne lozinke.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus adresa e-pošte", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Naziv web mjesta" }, - "whatWouldYouLikeToGenerate": { - "message": "Što želiš generirati?" - }, - "passwordType": { - "message": "Tip lozinke" - }, "service": { "message": "Usluga" }, @@ -2932,11 +2890,11 @@ "message": "Generiraj pseudonim e-pošte s vanjskom uslugom prosljeđivanja." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domena e-pošte", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Odaberi domenu koju podržava odabrani servis", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Ponovno pošalji obavijest" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Pogledaj sve mogućnosti prijave" + }, + "viewAllLoginOptionsV1": { "message": "Pogledaj sve mogućnosti prijave" }, "notificationSentDevice": { "message": "Obavijest je poslana na tvoj uređaj." }, + "aNotificationWasSentToYourDevice": { + "message": "Obavijest je poslana na tvoj uređaj" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Dobiti ćeš obavijest kada je tvoj zahtjev odobren" + }, + "needAnotherOptionV1": { + "message": "Trebaš drugu opciju?" + }, "loginInitiated": { "message": "Prijava pokrenuta" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Otvara u novom prozoru" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamti ovaj uređaj kako bi buduće prijave bile brže" + }, "deviceApprovalRequired": { "message": "Potrebno je odobriti uređaj. Odaberi metodu odobravanja:" }, + "deviceApprovalRequiredV2": { + "message": "Potrebno odobrenje uređaja" + }, + "selectAnApprovalOptionBelow": { + "message": "Odaberi opciju odobrenja" + }, "rememberThisDevice": { "message": "Zapamti ovaj uređaj" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Nedostaje e-pošta korisnika" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Nije pronađena e-pošta aktivnog korisnika. Odjava u tijeku..." + }, "deviceTrusted": { "message": "Uređaj pouzdan" }, @@ -3521,6 +3506,14 @@ "message": "Otključaj račun; otvara se u novom prozoru", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Kôd za provjeru jednokratne lozinka zasnovane na vremenu (TOTP) ", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Preostalo vrijeme koda za provjeru", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Unesi vjerodajnice za", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Pristupanje" }, + "loggedInExclamation": { + "message": "Prijava uspješna!" + }, "passkeyNotCopied": { "message": "Pristupni ključ neće biti kopiran" }, @@ -4211,7 +4207,7 @@ "message": "Najveća veličina datoteke je 500 MB" }, "deleteAttachmentName": { - "message": "Izbriši privitak", + "message": "Izbriši privitak $NAME$", "placeholders": { "name": { "content": "$1", @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtri" }, + "filterVault": { + "message": "Filtriraj trezor" + }, + "filterApplied": { + "message": "Uključen jedan filter" + }, + "filterAppliedPlural": { + "message": "Uključeno filtera: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Osobni podaci" }, @@ -4564,13 +4575,13 @@ "message": "Lokacija stavke" }, "fileSend": { - "message": "File Send" + "message": "Send datoteke" }, "fileSends": { "message": "Send datoteke" }, "textSend": { - "message": "Text Send" + "message": "Send teksta" }, "textSends": { "message": "Send tekstovi" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Prikaži broj prijedloga auto-ispune na ikoni proširenja" }, + "showQuickCopyActions": { + "message": "Prikaži akcije brzog kopiranja na trezoru" + }, "systemDefault": { "message": "Zadano sustavom" }, "enterprisePolicyRequirementsApplied": { "message": "Pravila tvrtke primijenjena su na ovu postavku" }, + "sshPrivateKey": { + "message": "Privatni ključ" + }, + "sshPublicKey": { + "message": "Javni ključ" + }, + "sshFingerprint": { + "message": "Otisak prsta" + }, + "sshKeyAlgorithm": { + "message": "Vrsta ključa" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Pokušaj ponovo" }, @@ -4632,160 +4670,238 @@ "noEditPermissions": { "message": "Nemaš prava za uređivanje ove stavke" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentifikacija" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Ispuni generiranu lozinku", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Lozinka re-generirana", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Spremi prijavu u Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Razmak", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "znak ˜", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "znak `", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "znak !", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "znak @", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "znak #", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "znak $", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "znak %", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "znak ^", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "znak &", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "znak *", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "lijeva zagrada (", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "desna zagrada )", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "donja crtica _", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "crtica -", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "znak +", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "znak =", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "znak {", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "znak }", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "znak [", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "zank ]", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "znak |", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "znak \\", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "znak :", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "znak ;", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "znak \"", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "znak '", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "znak <", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "znak >", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "znak ,", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "znak .", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "znak ?", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "znak /", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Mala slova" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Velika slova" }, "generatedPassword": { - "message": "Generated password" + "message": "Generiraj lozinku" + }, + "compactMode": { + "message": "Kompaktni način" + }, + "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" + }, + "wide": { + "message": "Široko" + }, + "extraWide": { + "message": "Ekstra široko" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index add9cf6ab89..0aea2c7eced 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -20,16 +20,16 @@ "message": "Fiók létrehozása" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Új vagyunk a Bitwardenben?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Bejelentkezés hozzáférési kulccsal" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Egyszeri bejelentkezés használata" }, "welcomeBack": { - "message": "Welcome back" + "message": "Üdvözlet újra" }, "setAStrongPassword": { "message": "Erős jelszó beállítása" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Licensz szám másolása" }, + "copyPrivateKey": { + "message": "Személyes kulcs másolása" + }, + "copyPublicKey": { + "message": "Nyilvános kulcs másolása" + }, + "copyFingerprint": { + "message": "Ujjlenyomat másolása" + }, "copyCustomField": { "message": "$FIELD$ másolása", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Automatikus kitöltés személyazonosság" }, + "fillVerificationCode": { + "message": "Ellenőrző kód kitöltése" + }, + "fillVerificationCodeAria": { + "message": "Ellenőrző Kód kitöltése", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Jelszó generálás (másolt)" }, @@ -438,9 +454,6 @@ "length": { "message": "Hossz" }, - "passwordMinLength": { - "message": "Minimum jelszó hosszúság" - }, "uppercase": { "message": "Nagybetűs (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimális speciális" }, - "avoidAmbChar": { - "message": "Félreérthető karakterek mellőzése", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Félreérthető karakterek mellőzése", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Bővítmény értékelése" }, - "rateExtensionDesc": { - "message": "Kérlek, fontold meg egy jó értékelés hagyását, ezzel segítve nekünk!" - }, "browserNotSupportClipboard": { "message": "A webböngésződ nem támogat könnyű vágólap másolást. Másold manuálisan inkább." }, @@ -846,7 +852,7 @@ "message": "Log in" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bejelentkezés a Bitwardenbe" }, "restartRegistration": { "message": "Restart registration" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Azonosítás elemek listázása a Fül oldalon a könnyű automatikus kitöltéshez." }, + "clickToAutofillOnVault": { + "message": "Kattintsunk az elemekre az automatikus kitöltéshez a Széf nézetben" + }, "clearClipboard": { "message": "Vágólap ürítése", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "FIGYELEM", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Figyelmeztetés", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Széf exportálás megerősítése" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Áthelyezés szervezethez" }, - "share": { - "message": "Megosztás" - }, "movedItemToOrg": { "message": "$ITEMNAME$ átkerült $ORGNAME$ szervezethez", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Add meg a 6 számjegyű ellenőrző kódot a hitelesítő alkalmazásodból." }, + "authenticationTimeout": { + "message": "Hitelesítési időkifutás" + }, + "authenticationSessionTimedOut": { + "message": "A hitelesítési munkamenet időkifutással lejárt. Indítsuk újra a bejelentkezési folyamatot." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ email címre elküldött 6 számjegyű ellenőrző kód megadása.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Ha egy bejelentkezési űrlap észlelésre került, az adatok automatikus kitöltése az oldal betöltésekor." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Az oldalbetöltésnél automatikus kitöltést a feltört vagy nem megbízhatató weboldalak kihasználhatják." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Személyazonosság" }, + "typeSshKey": { + "message": "SSH kulcs" + }, "newItemHeader": { "message": "Új $TYPE$", "placeholders": { @@ -1795,7 +1801,7 @@ "message": "Jelszó előzmények" }, "generatorHistory": { - "message": "Generator history" + "message": "Generátor előzmények" }, "clearGeneratorHistoryTitle": { "message": "Beenerátor előzmények kiürítése" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Biztonságos jegyzetek" }, + "sshKeys": { + "message": "SSH kulcsok" + }, "clear": { "message": "Kiürítés", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "Előzmények törlése" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Nincs megjeleníthető elem" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Mostanában nem lett semmi generálva." }, "remove": { "message": "Eltávolítás" @@ -2031,9 +2040,6 @@ "clone": { "message": "Klónozás" }, - "passwordGeneratorPolicyInEffect": { - "message": "Egy vagy több szervezeti szabály érinti a generátor beállításokat." - }, "passwordGenerator": { "message": "Jelszó generátor" }, @@ -2318,6 +2324,9 @@ "message": "Tartomány", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Letiltott tartományok" + }, "excludedDomains": { "message": "Kizárt domainek" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "A Bitwarden nem kéri a bejelentkezési adatok mentését ezeknél a tartományoknál az összes bejelentkezési fiókra vonatkozva. A változtatások életbe lépéséhez frissíteni kell az oldalt." }, + "blockedDomainsDesc": { + "message": "Az automatikus kitöltés és az egyéb kapcsolódó funkciók ezeken a webhelyeken nincsenek a kínálatban. A változtatások életbe lépéséhez frissíteni kell az oldalt." + }, + "autofillBlockedNotice": { + "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át vagy módosítsuk ezt a beállításokban." + }, + "autofillBlockedTooltip": { + "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át ezt a beállításokban." + }, "websiteItemLabel": { "message": "Webhely $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "A letiltott tartomány módosítások mentésre kerültek." + }, "excludedDomainsSavedSuccess": { "message": "A kizárt tartomány módosítások mentésre kerültek." }, @@ -2373,14 +2394,6 @@ "message": "Send részletek", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Send keresése", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send hozzáadása", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Szöveg" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Szöveg elrejtése alapértelmezetten" }, - "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." - }, "expired": { "message": "Lejárt" }, - "pendingDeletion": { - "message": "Függőben lévő törlés" - }, "passwordProtected": { "message": "Jelszóval védett" }, @@ -2456,24 +2462,9 @@ "message": "Send szerkesztése", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Milyen típusú ez a Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Barátságos név a Send leírására.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "A küldendő fájl." - }, "deletionDate": { "message": "Törlési dátum" }, - "deletionDateDesc": { - "message": "A Send véglegesen törölve lesz a meghatározott időpontban.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "A Send véglegesen törölve lesz ebben az időpontban.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Lejárati dátum" }, - "expirationDateDesc": { - "message": "Amennyiben be van állítva, a hozzáférés ehhez a Sendhez a meghatározott időpontban lejár", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 nap" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Egyedi" }, - "maximumAccessCount": { - "message": "Maximális elérési szám" - }, - "maximumAccessCountDesc": { - "message": "Beállítva a Küldés elérhetetlen lesz a meghatározott hozzáférések számának elérése után.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Opcionálisan megadhatunk egy jelszót a felhasználók számára a Küldés eléréséhez. ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Adjunk meg egy opcionális jelszót a címzetteknek a Send eléréséhez.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Személyes megjegyzések erről a Küldésről.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "A Send letiltásával senki nem férhet hozzá.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Mentéskor másoljuk a Küldés hivatkozását a vágólapra.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "A küldendő fájl." - }, - "sendHideText": { - "message": "Alapértelmezés szerint elrejti a Küldés szövegét.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuális elérési szám" - }, "createSend": { "message": "Új Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Mielőtt belevágnánk" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Naptár-stílusú dátumválasztáshoz", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "kattintás ide", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "az ablak megnyitásához", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "A megadott lejárati idő nem érvényes." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Hiba történt a törlési és a lejárati dátum mentésekor." }, - "hideEmail": { - "message": "Saját email cím elrejtése a címzettek elől." - }, "hideYourEmail": { "message": "Saját email cím elrejtése a megtekintések elől." }, - "sendOptionsPolicyInEffect": { - "message": "Egy vagy több szervezeti szabály érinti a Send opciókat." - }, "passwordPrompt": { "message": "Mesterjelszó ismételt megadás" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Hiba" }, - "regenerateUsername": { - "message": "Felhasználónév ismételt geneálása" + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Felhasználónév generálása" @@ -2877,7 +2824,7 @@ "generateEmail": { "message": "Email generálása" }, - "generatorBoundariesHint": { + "spinboxBoundariesHint": { "message": "Az érték legyen $MIN$ és $MAX$ között.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Felhasználónév típusa" + "passwordLengthRecommendationHint": { + "message": " Használjunk $RECOMMENDED$ vagy több karaktert egy erős jelszó előállításához.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " $RECOMMENDED$ vagy több szó erős jelmondat generálásához.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "További címzési email cím", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Webhelynév" }, - "whatWouldYouLikeToGenerate": { - "message": "Mit szeretnénk generálni?" - }, - "passwordType": { - "message": "Jelszótípus" - }, "service": { "message": "Szolgáltatás" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Értesítés újraküldése" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Összes bejelentkezési opció megtekintése" + }, + "viewAllLoginOptionsV1": { "message": "Összes bejelentkezési opció megtekintése" }, "notificationSentDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, + "aNotificationWasSentToYourDevice": { + "message": "Egy értesítés lett elküldve az eszközre." + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Ellenőrizzük, hogy a széf feloldásra került és az ujjlenyomat kifejezés egyezik a másik eszközön levővel." + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "A kérelem jóváhagyása után értesítés érkezik." + }, + "needAnotherOptionV1": { + "message": "Másik opció szükséges?" + }, "loginInitiated": { "message": "A bejelentkezés elindításra került." }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Megnyitás új ablakban" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Emlékezés az eszközre, hogy zökkenőmentes legyen a jövőbeni bejelentkezés" + }, "deviceApprovalRequired": { "message": "Az eszköz jóváhagyása szükséges. Válasszunk egy jóváhagyási lehetőséget lentebb:" }, + "deviceApprovalRequiredV2": { + "message": "Eszköz jóváhagyás szükséges" + }, + "selectAnApprovalOptionBelow": { + "message": "Válasszunk lentebb egy jóváhagyási lehetőséget." + }, "rememberThisDevice": { "message": "Eszköz megjegyzése" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "A felhasználói email cím hiányzik." }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Az aktív felhasználói email cím nem található. Jelentkezzünk ki." + }, "deviceTrusted": { "message": "Az eszköz megbízható." }, @@ -3521,6 +3506,14 @@ "message": "Oldjuk fel a fiók zárolását, új ablakban nyílik meg.", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Időalapú, egyszeri jelszó ellenőrző kód", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "A jelenlegi TOTP lejártáig hátralévő idő", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Töltse kia hitelesítő adatokat", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Elérés" }, + "loggedInExclamation": { + "message": "Megtörtént a bejelentkezés." + }, "passkeyNotCopied": { "message": "A hozzáférési kulcs nem kerül másolásra." }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Szűrők" }, + "filterVault": { + "message": "Széf szűrése" + }, + "filterApplied": { + "message": "Egy szűrő került alkalmazásra." + }, + "filterAppliedPlural": { + "message": "$COUNT$ szűrő került alkalmazásra.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Személyes adatok" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Az automatikus bejelentkezési kitöltési javaslatok számának megjelenítése a bővítmény ikonján" }, + "showQuickCopyActions": { + "message": "Gyors másolási műveletek megjelenítése a Széfen" + }, "systemDefault": { "message": "Rendszer alapértelmezett" }, "enterprisePolicyRequirementsApplied": { "message": "Erre a beállításra a vállalkozás rendszabály követelmények lettek alkalmazva." }, + "sshPrivateKey": { + "message": "Személyes kulcs" + }, + "sshPublicKey": { + "message": "Nyilvános kulcs" + }, + "sshFingerprint": { + "message": "Ujjlenyomat" + }, + "sshKeyAlgorithm": { + "message": "Kulcs típusa" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Újra" }, @@ -4632,160 +4670,238 @@ "noEditPermissions": { "message": "Nincs jogosulltság ezen elem szerkesztéséheu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "A biometrikus feloldás nem érhető el, mert a Bitwarden asztali alkalmazás be van zárva." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authenticating": { "message": "Authenticating" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Generált jelszó kitöltés", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "A jelszó generálásra került.", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Bejelentkezés mentése a Bitwardenbe?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Szóköz", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Hullám karakter", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Backtick karakter", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Felkiáltójel", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "@ karakter", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "# karakter", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "$ karakter", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "% karakter", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "^ karakter", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "& karakter", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "* karakter", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "( karakter", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": ") karakter", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "_ karakter", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "- karakter", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "+ karakter", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "= karakter", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "{ karakter", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "} karakter", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "[ karakter", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "] karakter", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "| karakter", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "\\ karakter", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": ": karakter", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "; karakter", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "\" karakter", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "' karakter", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "< karakter", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "> karakter", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": ", karakter", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": ". karakter", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "? karakter", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "/ karakter", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Kisbetű" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Nagybetű" }, "generatedPassword": { - "message": "Generated password" + "message": "Generált jelszó" + }, + "compactMode": { + "message": "Kompakt mód" + }, + "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" + }, + "wide": { + "message": "Széles" + }, + "extraWide": { + "message": "Extra széles" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 87a50d571b1..82776a8e82b 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Pengelola Sandi", + "message": "Pengelola Sandi Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -14,28 +14,28 @@ "message": "Masuk atau buat akun baru untuk mengakses brankas Anda." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Undangan diterima" }, "createAccount": { "message": "Buat Akun" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Baru menggunakan Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Masuk dengan kunci sandi" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Gunakan masuk tunggal" }, "welcomeBack": { - "message": "Welcome back" + "message": "Selamat datang kembali" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Atur sebuah kata sandi yang kuat" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Selesaikan membuat akun Anda dengan mengatur sebuah kata sandi" }, "enterpriseSingleSignOn": { "message": "SSO Perusahaan" @@ -62,7 +62,7 @@ "message": "Petunjuk kata sandi utama dapat membantu Anda mengingat kata sandi Anda jika Anda melupakannya." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Jika Anda lupa kata sandi Anda, petunjuk kata sandi dapat dikirim ke surel Anda. Maksimal $CURRENT$/$MAXIMUM$ karakter.", "placeholders": { "current": { "content": "$1", @@ -81,10 +81,10 @@ "message": "Petunjuk Kata Sandi Utama (opsional)" }, "joinOrganization": { - "message": "Join organization" + "message": "Bergabung ke organisasi" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Bergabung ke $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -93,7 +93,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Selesaikan penggabungan ke organisasi ini dengan mengatur sebuah kata sandi utama." }, "tab": { "message": "Tab" @@ -120,7 +120,7 @@ "message": "Salin Kata Sandi" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Salin frasa sandi" }, "copyNote": { "message": "Salin Catatan" @@ -138,22 +138,31 @@ "message": "Salin Kode Keamanan" }, "copyName": { - "message": "Copy name" + "message": "Salin nama" }, "copyCompany": { - "message": "Copy company" + "message": "Salin perusahaan" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Salin nomor Keamanan Sosial" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Salin nomor paspor" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Salin nomor lisensi" + }, + "copyPrivateKey": { + "message": "Salin kunci pribadi" + }, + "copyPublicKey": { + "message": "Salin kunci publik" + }, + "copyFingerprint": { + "message": "Salin sidik jari" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Salin $FIELD$", "placeholders": { "field": { "content": "$1", @@ -162,13 +171,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Salin situs web" }, "copyNotes": { - "message": "Copy notes" + "message": "Salin catatan" }, "fill": { - "message": "Fill", + "message": "Isikan", "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identitas" }, + "fillVerificationCode": { + "message": "Isikan kode verifikasi" + }, + "fillVerificationCodeAria": { + "message": "Isikan Kode Verifikasi", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Membuat Kata Sandi (tersalin)" }, @@ -223,16 +239,16 @@ "message": "Tambah Item" }, "accountEmail": { - "message": "Account email" + "message": "Akun surel" }, "requestHint": { - "message": "Request hint" + "message": "Minta petunjuk" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Minta petunjuk kata sandi" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "Masukkan alamat surel akun Anda dan petunjuk kata sandi Anda akan dikirimkan kepada Anda" }, "passwordHint": { "message": "Petunjuk Kata Sandi" @@ -280,7 +296,7 @@ "message": "Lanjut ke pasar ekstensi peramban?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "Bantu orang lain menemukan apakah Bitwarden sesuai untuk mereka. Kunjungi toko ekstensi peramban Anda dan tinggalkan sebuah ulasan sekarang." }, "changeMasterPasswordOnWebConfirmation": { "message": "Anda dapat merubah sandi utama di aplikasi Bitwarden web." @@ -300,43 +316,43 @@ "message": "Keluar" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Tentang Bitwarden" }, "about": { "message": "Tentang" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Selebihnya dari Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Lanjutkan ke bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden untuk Bisnis" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "message": "Pengotentikasi 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": "Pengotentikasi Bitwarden membolehkan Anda untuk menyimpan kunci pengotentikasi dan menghasilkan kode TOTP untuk alur verifikasi dua-langkah. Pelajari lebih lanjut di situs web bitwarden.com" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Pengelola Rahasia Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Simpan, kelola, dan bagikan secara aman rahasia pengembang dengan Pengelola Rahasia Bitwarden. Pelajari lebih lanjut di situs web 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": "Buat pengalaman masuk yang lancar dan aman dari kata sandi tradisional dengan Passwordless.dev. Pelajari lebih lanjut di situs web bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden Keluarga Gratis" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Anda berhak untuk Bitwarden Keluarga Gratis. Tukarkan penawaran ini hari ini di aplikasi web." }, "version": { "message": "Versi" @@ -357,22 +373,22 @@ "message": "Sunting Folder" }, "newFolder": { - "message": "New folder" + "message": "Folder baru" }, "folderName": { - "message": "Folder name" + "message": "Nama folder" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Sarangkan sebuah folder dengan menambahkan nama folder induk diikuti dengan sebuah \"/\". Contoh: Sosial/Forum" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Tidak ada folder yang ditambahkan" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "Buat folder untuk mengorganisasi benda-benda di brankas Anda" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "Apakah Anda yakin akan menghapus folder ini selamanya?" }, "deleteFolder": { "message": "Hapus Folder" @@ -415,7 +431,7 @@ "message": "Secara otomatis membuat sandi yang kuat dan unik untuk info masuk Anda." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Aplikasi web Bitwarden" }, "importItems": { "message": "Impor Item" @@ -427,7 +443,7 @@ "message": "Buat Kata Sandi" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Buat frasa sandi" }, "regeneratePassword": { "message": "Buat Ulang Kata Sandi" @@ -438,9 +454,6 @@ "length": { "message": "Panjang" }, - "passwordMinLength": { - "message": "Panjang kata sandi minimum" - }, "uppercase": { "message": "Huruf besar (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -458,11 +471,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "include": { - "message": "Include", + "message": "Sertakan", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Sertakan karakter huruf kapital", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -470,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Sertakan karakter huruf kecil", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -478,7 +491,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Sertakan angka", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -486,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Sertakan karakter khusus", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -512,16 +525,12 @@ "minSpecial": { "message": "Spesial Minimum" }, - "avoidAmbChar": { - "message": "Hindari Karakter Ambigu", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Hindari karakter ambigu", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Persyaratan kebijakan perusahaan telah diterapkan ke pilihan penghasil Anda.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -555,19 +564,19 @@ "message": "Favorit" }, "unfavorite": { - "message": "Unfavorite" + "message": "Batalkan favorit" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Benda telah ditambahkan ke kesukaan" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Benda telah dihapus dari kesukaan" }, "notes": { "message": "Catatan" }, "privateNote": { - "message": "Private note" + "message": "Catatan pribadi" }, "note": { "message": "Catatan" @@ -588,10 +597,10 @@ "message": "Luncurkan" }, "launchWebsite": { - "message": "Launch website" + "message": "Buka situs web" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Buka situs web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -624,7 +633,7 @@ "message": "Batas waktu sesi" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "Batas waktu brankas" }, "otherOptions": { "message": "Opsi lainnya" @@ -632,9 +641,6 @@ "rateExtension": { "message": "Nilai Ekstensi" }, - "rateExtensionDesc": { - "message": "Mohon pertimbangkan membantu kami dengan ulasan yang baik!" - }, "browserNotSupportClipboard": { "message": "Peramban Anda tidak mendukung menyalin clipboard dengan mudah. Salin secara manual." }, @@ -645,13 +651,13 @@ "message": "Brankas Anda terkunci. Verifikasi kata sandi utama Anda untuk melanjutkan." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Brankas Anda terkunci" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Akun Anda terkunci" }, "or": { - "message": "or" + "message": "atau" }, "unlock": { "message": "Buka Kunci" @@ -676,7 +682,7 @@ "message": "Batas Waktu Brankas" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Batas waktu" }, "lockNow": { "message": "Kunci Sekarang" @@ -730,16 +736,16 @@ "message": "Keamanan" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Konfirmasi kata sandi utama" }, "masterPassword": { - "message": "Master password" + "message": "Kata sandi utama" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Kata sandi utama Anda tidak dapat dipulihkan jika Anda melupakannya!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Petunjuk kata sandi utama" }, "errorOccurred": { "message": "Terjadi kesalahan" @@ -773,10 +779,10 @@ "message": "Akun baru Anda telah dibuat! Sekarang Anda bisa masuk." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Akun baru Anda telah dibuat!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Anda telah masuk!" }, "youSuccessfullyLoggedIn": { "message": "Anda berhasil masuk" @@ -791,7 +797,7 @@ "message": "Kode verifikasi diperlukan." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "Otentikasi dibatalkan atau terlalu lama. Mohon coba kembali." }, "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" @@ -819,16 +825,16 @@ "message": "Pindai kode QR autentikator dari laman ini" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Buat verifikasi dua-langkah lancar" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden dapat menyimpan dan mengisikan kode verifikasi dua-langkah. Salin dan tempel kunci ke kolom ini." }, "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 dapat menyimpan dan mengisikan kode verifikasi dua-langkah. Pilih ikon kamera untuk mengambil tangkapan layar dari kode QR otentikasi dari situs web ini, atau salin dan tempel kunci ke kolom ini." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Pelajari lebih lanjut tentang pengotentikasi" }, "copyTOTP": { "message": "Salin kunci Autentikator (TOTP)" @@ -837,28 +843,28 @@ "message": "Keluar" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Anda telah keluar dari akun Anda." }, "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, "logIn": { - "message": "Log in" + "message": "Masuk" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Masuk ke Bitwarden" }, "restartRegistration": { - "message": "Restart registration" + "message": "Mulai ulang pendaftaran" }, "expiredLink": { - "message": "Expired link" + "message": "Tautan telah kadaluwarsa" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Mohon mulai ulang pendaftaran atau coba masuk." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Anda mungkin telah memiliki sebuah akun" }, "logOutConfirmation": { "message": "Anda yakin ingin keluar?" @@ -882,10 +888,10 @@ "message": "Info masuk dua langkah membuat akun Anda lebih aman dengan mengharuskan Anda memverifikasi info masuk Anda dengan peranti lain seperti kode keamanan, aplikasi autentikasi, SMK, panggilan telepon, atau email. Info masuk dua langkah dapat diaktifkan di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Buat akun Anda lebih aman dengan mengatur masuk dua-langkah pada aplikasi web Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Lanjutkan ke aplikasi web?" }, "editedFolder": { "message": "Folder yang disunting" @@ -928,7 +934,7 @@ "message": "URl Baru" }, "addDomain": { - "message": "Add domain", + "message": "Tambah domain", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -972,31 +978,34 @@ "message": "Tanya untuk penambahan login" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "Simpan ke pilihan brankas" }, "addLoginNotificationDesc": { "message": "\"Notifikasi Penambahan Info Masuk\" secara otomatis akan meminta Anda untuk menyimpan info masuk baru ke brankas Anda saat Anda masuk untuk pertama kalinya." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "message": "Tanyakan untuk menambah sebuah benda jika benda itu tidak ditemukan di brankas Anda. Diterapkan ke seluruh akun yang telah masuk." }, "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "message": "Tampilkan kartu sebagai saran isi otomatis pada tampilan Brankas" }, "showCardsCurrentTab": { - "message": "Show cards on Tab page" + "message": "Tamplikan kartu pada halaman Tab" }, "showCardsCurrentTabDesc": { - "message": "List card items on the Tab page for easy autofill." + "message": "Buat tampilan daftar benda dari kartu pada halaman Tab untuk isi otomatis yang mudah." }, "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "message": "Tampilkan identitas sebagai saran isi otomatis pada tampilan Brankas" }, "showIdentitiesCurrentTab": { - "message": "Show identities on Tab page" + "message": "Tampilkan identitas pada halaman Tab" }, "showIdentitiesCurrentTabDesc": { - "message": "List identity items on the Tab page for easy autofill." + "message": "Buat tampilan daftar benda dari identitas pada halaman Tab untuk isi otomatis yang mudah." + }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" }, "clearClipboard": { "message": "Hapus Papan Klip", @@ -1013,19 +1022,19 @@ "message": "Iya, Simpan Sekarang" }, "enableChangedPasswordNotification": { - "message": "Ask to update existing login" + "message": "Tanyakan untuk memperbarui masuk yang sudah ada" }, "changedPasswordNotificationDesc": { - "message": "Ask to update a login's password when a change is detected on a website." + "message": "Tanyakan untuk memperbarui kata sandi masuk ketika mendeteksi perubahan pada situs web." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "Tanyakan untuk memperbarui kata sandi masuk ketika mendeteksi perubahan pada situs web. Diterapkan ke semua akun yang telah masuk." }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "Tanyakan untuk menyimpan dan menggunakan kunci sandi" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "Tanyakan untuk menyimpan kunci sandi baru atau masuk dengan kunci sandi yang tersimpan di brankas Anda. Diterapkan ke semua akun yang telah masuk." }, "notificationChangeDesc": { "message": "Apakah Anda ingin memperbarui kata sandi ini di Bitwarden?" @@ -1034,22 +1043,22 @@ "message": "Iya, Perbarui Sekarang" }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the autofill request." + "message": "Buka brankan Bitwarden Anda untuk melengkapi permintaan isi otomatis." }, "notificationUnlock": { - "message": "Unlock" + "message": "Buka" }, "additionalOptions": { - "message": "Additional options" + "message": "Pilihan tambahan" }, "enableContextMenuItem": { - "message": "Show context menu options" + "message": "Tampilkan pilihan menu konteks" }, "contextMenuItemDesc": { - "message": "Use a secondary click to access password generation and matching logins for the website." + "message": "Gunakan tombol sekunder untuk mengakses pembuat kata sandi dan mencocokkan login untuk situs web." }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "Gunakan tombol sekunder untuk mengakses pembuat kata sandi dan mencocokkan login untuk situs web. Diterapkan ke semua akun yang telah masuk." }, "defaultUriMatchDetection": { "message": "Deteksi Kecocokan URI Bawaan", @@ -1065,7 +1074,7 @@ "message": "Ubah tema warna aplikasi." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "Ubah warna tema aplikasi. Diterapkan ke semua akun yang telah masuk." }, "dark": { "message": "Gelap", @@ -1080,7 +1089,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "Ekspor dari" }, "exportVault": { "message": "Ekspor Brankas" @@ -1089,33 +1098,37 @@ "message": "Format Berkas" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Ekpor berkas ini akan akan dilindungi oleh kata sandi dan membutuhkan kata sandi berkas untuk mendekripsikannya." }, "filePassword": { - "message": "File password" + "message": "Kata sandi berkas" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Kata sandi ini akan digunakan untuk mengekspor dan mengimpor berkas ini" }, "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": "Gunakan kunci enkripsi akun Anda, diturunkan dari nama pengguna akun Anda dan kata sandi utama, untuk mengenkripsi ekspor dan membatasi impor menjadi hanya akun Bitwarden saat ini." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Atur kata sandi berkas untuk mengenkripsi ekspor dan mengimpornya ke sebarang akun Bitwarden menggunakan kata sandi untuk dekripsi." }, "exportTypeHeading": { - "message": "Export type" + "message": "Jenis ekspor" }, "accountRestricted": { - "message": "Account restricted" + "message": "Akun dibatasi" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"Kata sandi berkas\" dan \"Konfirmasi kata sandi berkas\" tidak cocok." }, "warning": { "message": "PERINGATAN", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Peringatan", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Konfirmasi Ekspor Brankas" }, @@ -1135,14 +1148,11 @@ "message": "Dibagikan" }, "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 untuk Bisnis membolehkan Anda untuk membagikan benda-benda di brankas Anda dengan orang lain dengan menggunakan sebuah organisasi. Pelajari lebih lanjut pada situs web bitwarden.com." }, "moveToOrganization": { "message": "Pindah ke Organisasi" }, - "share": { - "message": "Bagikan" - }, "movedItemToOrg": { "message": "$ITEMNAME$ pindah ke $ORGNAME$", "placeholders": { @@ -1196,7 +1206,7 @@ "message": "Berkas" }, "fileToShare": { - "message": "File to share" + "message": "Berkas untuk dibagikan" }, "selectFile": { "message": "Pilih berkas." @@ -1208,7 +1218,7 @@ "message": "Fitur Tidak Tersedia" }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "Kunci enkripsi migrasi dibutuhkan. Silakan masuk melalui brankas web untuk memperbarui kunci enkripsi Anda." }, "premiumMembership": { "message": "Keanggotaan Premium" @@ -1232,10 +1242,10 @@ "message": "1 GB penyimpanan berkas yang dienkripsi." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Akses darurat." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "Pilihan masuk dua-langkah yang dipatenkan seperti YubiKey dan Duo." }, "ppremiumSignUpReports": { "message": "Kebersihan kata sandi, kesehatan akun, dan laporan kebocoran data untuk tetap menjaga keamanan brankas Anda." @@ -1256,7 +1266,7 @@ "message": "Anda dapat membeli keanggotaan premium di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Anda dapat membeli Premium dari pilihan akun Anda pada aplikasi web Bitwarden." }, "premiumCurrentMember": { "message": "Anda adalah anggota premium!" @@ -1265,7 +1275,7 @@ "message": "Terima kasih telah mendukung Bitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "Tingkatkan ke Premium dan dapatkan:" }, "premiumPrice": { "message": "Semua itu hanya $PRICE$ /tahun!", @@ -1277,7 +1287,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "Semua itu hanya $PRICE$ tiap tahun!", "placeholders": { "price": { "content": "$1", @@ -1295,7 +1305,7 @@ "message": "Jika info masuk Anda memiliki kunci autentikasi yang menyertainya, kode verifikasi TOTP akan disalin secara otomatis ke clipboard Anda setiap kali Anda mengisi info masuk secara otomatis." }, "enableAutoBiometricsPrompt": { - "message": "Ask for biometrics on launch" + "message": "Tanyakan untuk biometrik pada saat diluncurkan" }, "premiumRequired": { "message": "Membutuhkan Keanggotaan Premium" @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Masukkan 6 digit kode verifikasi dari aplikasi autentikasi Anda." }, + "authenticationTimeout": { + "message": "Batas waktu otentikasi" + }, + "authenticationSessionTimedOut": { + "message": "Sesi otentikasi telah berakhir. Harap mulai ulang proses masuk." + }, "enterVerificationCodeEmail": { "message": "Masukkan 6 digit kode verifikasi yang dikirim melalui email ke $EMAIL$.", "placeholders": { @@ -1370,17 +1386,17 @@ "message": "Aplikasi Otentikasi" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Masukkan kode yang dihasilkan dari aplikasi pengotentikasi seperti Pengotentikasi Bitwarden.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "Kunci Keamanan OTP Yubico" }, "yubiKeyDesc": { "message": "Gunakan YubiKey untuk mengakses akun Anda. Bekerja dengan YubiKey 4, 4 Nano, 4C, dan peranti NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Masukkan kode yang dihasilkan oleh Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1397,7 +1413,7 @@ "message": "Email" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Masukkan kode yang dikirim ke surel Anda." }, "selfHostedEnvironment": { "message": "Lingkungan Penyedia Personal" @@ -1406,13 +1422,13 @@ "message": "Tetapkan URL dasar penyedia personal pemasangan Bitwarden Anda." }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Tentukan URL dasar dari pemasangan Bitwarden di server Anda. Contoh: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Untuk pengaturan tingkat lanjut, Anda dapat menentukan URL dasar dari setiap layanan secara terpisah." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Anda harus menambahkan antara URL dasar server atau paling tidak satu lingkungan ubahsuai." }, "customEnvironment": { "message": "Lingkungan Khusus" @@ -1424,7 +1440,7 @@ "message": "URL Server" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL server yang dihosting mandiri", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1446,47 +1462,47 @@ "message": "URL dari semua lingkungan telah disimpan." }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "Tampilkan menu isi otomatis pada kolom formulir", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Saran isi otomatis" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "Tampilkan saran isi otomatis pada kolom formulir" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Tampilkan identitas sebagai saran" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Tampilkan kartu sebagai saran" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Tampilkan saran ketika ikon dipilih" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Diterapkan ke semua akun yang telah masuk." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "Matikan pengaturan pengelola kata sandi bawaan peramban Anda untuk menghindari benturan." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "Sunting pengaturan peramban." }, "autofillOverlayVisibilityOff": { - "message": "Off", + "message": "Mati", "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "Ketika kolom dipilih (ketika difokuskan)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "Ketika ikon isi otomatis dipilih", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Isi otomatis ketika halaman dimuat" }, "enableAutoFillOnPageLoad": { "message": "Aktifkan Isi-Otomatis Saat Memuat Laman" @@ -1494,27 +1510,14 @@ "enableAutoFillOnPageLoadDesc": { "message": "Jika formulir info masuk terdeteksi, secara otomatis melakukan pengisian otomatis ketika memuat laman web." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit autofill on page load." + "message": "Situs web yang disusupi atau tidak terpercaya dapat memanfaatkan isi otomatis ketika halaman dimuat." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Pelajari lebih lanjut tentang risiko" }, "learnMoreAboutAutofill": { - "message": "Learn more about autofill" + "message": "Pelajari lebih lanjut tentang isi otomatis" }, "defaultAutoFillOnPageLoad": { "message": "Konfigurasi autofill standard untuk item login." @@ -1541,13 +1544,13 @@ "message": "Buka brankas di bilah samping" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Isi otomatis login yang terakhir digunakan untuk situs web saat ini" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Isi otomatis kartu yang terakhir digunakan untuk situs web saat ini" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Isi otomatis identitas yang terakhir digunakan untuk situs web saat ini" }, "commandGeneratePasswordDesc": { "message": "Buat dan salin kata sandi acak baru ke papan klip." @@ -1580,14 +1583,14 @@ "message": "Boolean" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Kotak centang" }, "cfTypeLinked": { "message": "Terhubung", "description": "This describes a field that is 'linked' (tied) to another field." }, "linkedValue": { - "message": "Linked value", + "message": "Nilai terkait", "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { @@ -1597,19 +1600,19 @@ "message": "Peramban ini tidak bisa memproses permintaan U2F di jendela popup ini. Apakah Anda ingin membuka popup ini di jendela baru sehingga Anda dapat masuk menggunakan U2F?" }, "enableFavicon": { - "message": "Show website icons" + "message": "Tampilkan ikon situs web" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "Tampilkan sebuah gambar yang dapat dikenali di setiap masuk." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Tampilkan sebuah gambar yang dapat dikenali di sebelah tiap login. Diterapkan ke semua akun yang telah masuk." }, "enableBadgeCounter": { - "message": "Show badge counter" + "message": "Tampilkan hitungan di lencana" }, "badgeCounterDesc": { - "message": "Indicate how many logins you have for the current web page." + "message": "Tunjukkan seberapa banyak login yang Anda miliki untuk halaman web saat ini." }, "cardholderName": { "message": "Nama Pemegang Kartu" @@ -1687,7 +1690,7 @@ "message": "Dr" }, "mx": { - "message": "Mx" + "message": "Yth" }, "firstName": { "message": "Nama Depan" @@ -1764,8 +1767,11 @@ "typeIdentity": { "message": "Identitas" }, + "typeSshKey": { + "message": "Kunci SSH" + }, "newItemHeader": { - "message": "New $TYPE$", + "message": "$TYPE$ baru", "placeholders": { "type": { "content": "$1", @@ -1774,7 +1780,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Sunting $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1783,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Lihat $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1795,13 +1801,13 @@ "message": "Riwayat Kata Sandi" }, "generatorHistory": { - "message": "Generator history" + "message": "Riwayat penghasil" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Bersihkan riwayat penghasil" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Jika Anda melanjutkan, semua daftar akan dihapus selamanya dari riwayat penghasil. Apakah Anda yakin ingin melanjutkan?" }, "back": { "message": "Kembali" @@ -1810,7 +1816,7 @@ "message": "Koleksi" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ koleksi", "placeholders": { "count": { "content": "$1", @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Catatan Aman" }, + "sshKeys": { + "message": "Kunci SSH" + }, "clear": { "message": "Bersihkan", "description": "To clear something out. example: To clear browser history." @@ -1863,7 +1872,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Domain utama (disarankan)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1917,13 +1926,13 @@ "message": "Tidak ada sandi yang dapat dicantumkan." }, "clearHistory": { - "message": "Clear history" + "message": "Bersihkan riwayat" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Tidak ada yang dapat ditampilkan" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Anda belum menghasilkan apapun akhir-akhir ini" }, "remove": { "message": "Hapus" @@ -1984,16 +1993,16 @@ "message": "Buka kunci dengan PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "Atur PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "Atur PIN" }, "setYourPinCode": { "message": "Setel kode PIN Anda untuk membuka kunci Bitwarden. Pengaturan PIN Anda akan diatur ulang jika Anda pernah keluar sepenuhnya dari aplikasi." }, "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": "PIN Anda akan digunakan untuk membuka Bitwarden alih-alih dengan kata sandi utama Anda. PIN Anda akan diatur ulang apabila Anda pernah keluar sepenuhnya dari Bitwarden." }, "pinRequired": { "message": "Membutuhkan kode PIN." @@ -2002,13 +2011,13 @@ "message": "Kode PIN tidak valid." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Terlalu banyak usaha memasukkan PIN yang gagal. Mengeluarkan dari sesi." }, "unlockWithBiometrics": { "message": "Buka kunci dengan biometrik" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Buka dengan kata sandi utama" }, "awaitDesktop": { "message": "Menunggu konfirmasi dari desktop" @@ -2020,7 +2029,7 @@ "message": "Kunci dengan kata sandi utama saat peramban dimulai ulang" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Memerlukan kata sandi utama ketika mulai ulang peramban" }, "selectOneCollection": { "message": "Anda harus memilih setidaknya satu koleksi." @@ -2031,37 +2040,34 @@ "clone": { "message": "Duplikat" }, - "passwordGeneratorPolicyInEffect": { - "message": "Satu atau lebih kebijakan organisasi mempengaruhi pengaturan pembuat sandi Anda." - }, "passwordGenerator": { - "message": "Password generator" + "message": "Pembuat kata sandi" }, "usernameGenerator": { - "message": "Username generator" + "message": "Pembuat nama pengguna" }, "useThisPassword": { - "message": "Use this password" + "message": "Gunakan kata sandi ini" }, "useThisUsername": { - "message": "Use this username" + "message": "Gunakan nama pengguna ini" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "Kata sandi aman berhasil dibuat! Jangan lupa untuk memperbarui kata sandi Anda di situs web." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Gunakan penghasil", "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": "untuk membuat sebuah kata sandi unit yang kuat", "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'" }, "vaultTimeoutAction": { "message": "Tindakan Batas Waktu Brankas" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Batas waktu tindakan" }, "lock": { "message": "Kunci", @@ -2090,7 +2096,7 @@ "message": "Item Yang Dipulihkan" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Sudah memiliki akun?" }, "vaultTimeoutLogOutConfirmation": { "message": "Keluar akan menghapus semua akses ke brankas Anda dan membutuhkan otentikasi daring setelah periode batas waktu tertentu. Apakah Anda yakin ingin menggunakan pengaturan ini?" @@ -2102,7 +2108,7 @@ "message": "Isi Otomatis dan Simpan" }, "fillAndSave": { - "message": "Fill and save" + "message": "Isikan dan simpan" }, "autoFillSuccessAndSavedUri": { "message": "Item yang Diisi Otomatis dan URI Tersimpan" @@ -2111,16 +2117,16 @@ "message": "Item Terisi Otomatis" }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "Peringatan: Ini adalah halaman HTTP yang tidak aman, dan setiap informasi yang Anda kirim dapat berpotensi terlihat dan diubah oleh orang lain. Login ini awalnya disimpan di halaman aman (HTTPS) " }, "insecurePageWarningFillPrompt": { - "message": "Do you still wish to fill this login?" + "message": "Anda masih ingin mengisi login ini?" }, "autofillIframeWarning": { - "message": "The form is hosted by a different domain than the URI of your saved login. Choose OK to autofill anyway, or Cancel to stop." + "message": "Formulir dihosting oleh domain yang berbeda dari URI login yang telah Anda simpan. Pilih OK untuk tetap mengisi otomatis, atau Batalkan untuk menghentikan." }, "autofillIframeWarningTip": { - "message": "To prevent this warning in the future, save this URI, $HOSTNAME$, to your Bitwarden login item for this site.", + "message": "Untuk mencegah peringatan ini di masa depan, simpan URI ini, $HOSTNAME$, ke benda login Bitwarden untuk situs ini.", "placeholders": { "hostname": { "content": "$1", @@ -2132,13 +2138,13 @@ "message": "Atur Kata Sandi Utama" }, "currentMasterPass": { - "message": "Current master password" + "message": "Kata sandi utama saat ini" }, "newMasterPass": { - "message": "New master password" + "message": "Kata sandi utama baru" }, "confirmNewMasterPass": { - "message": "Confirm new master password" + "message": "Konfirmasi kata sandi utama baru" }, "masterPasswordPolicyInEffect": { "message": "Satu atau lebih kebijakan organisasi membutuhkan kata sandi utama Anda untuk memenuhi persyaratan berikut:" @@ -2183,19 +2189,19 @@ "message": "Kata sandi utama Anda yang baru tidak memenuhi persyaratan kebijakan." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Dapatkan saran, pengumuman, dan kesempatan penelitian dari Bitwarden di kotak masuk Anda." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Berhenti berlangganan" }, "atAnyTime": { - "message": "at any time." + "message": "kapanpun." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Dengan melanjutkan, Anda menyetujui" }, "and": { - "message": "and" + "message": "dan" }, "acceptPolicies": { "message": "Dengan mencentang kotak ini, Anda menyetujui yang berikut:" @@ -2216,10 +2222,10 @@ "message": "Oke" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Galat Penyegaran Token Akses" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Tidak ada token penyegaran atau kunci API yang ditemukan. Harap coba keluar dan masuk kembali." }, "desktopSyncVerificationTitle": { "message": "Verifikasi sinkronisasi desktop" @@ -2258,10 +2264,10 @@ "message": "Akun tidak cocok" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "Kunci biometrik tidak cocok" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "Gagal membuka dengan biometrik. Kunci rahasia biometrik gagal membuka brankas. Harap coba atur biometrik lagi." }, "biometricsNotEnabledTitle": { "message": "Biometrik tidak diaktifkan" @@ -2276,22 +2282,22 @@ "message": "Biometrik peramban tidak didukung di perangkat ini." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Pengguna terkunci atau telah keluar" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Harap buka kunci pengguna ini di aplikasi desktop dan coba kembali." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "Buka dengan biometrik tidak tersedia" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "Buka dengan biometrik tidak tersedia untuk saat ini. Mohon coba kembali nanti." }, "biometricsFailedTitle": { - "message": "Biometrics failed" + "message": "Biometrik gagal" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "Biometrik tidak dapat diselesaikan, pertimbangkan untuk menggunakan sebuah kata sandi utama atau keluar. Jika hal ini tetap berlangsung, mohon hubungi dukungan Bitwarden." }, "nativeMessaginPermissionErrorTitle": { "message": "Izin tidak diberikan" @@ -2312,12 +2318,15 @@ "message": "Kebijakan organisasi memengaruhi opsi kepemilikan Anda." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "Sebuah kebijakan organisasi telah menghalangi mengimpor benda-benda ke brankas pribadi Anda." }, "domainsTitle": { - "message": "Domains", + "message": "Domain", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domain yang Dikecualikan" }, @@ -2325,10 +2334,19 @@ "message": "Bitwarden tidak akan meminta untuk menyimpan detail login untuk domain ini. Anda harus menyegarkan halaman agar perubahan diterapkan." }, "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 tidak akan meminta untuk menyimpan rincian login untuk domain tersebut. Anda harus menyegarkan halaman agar perubahan diterapkan." + }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "Situs web $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "Perubahan domain yang diabaikan telah disimpan" }, "limitSendViews": { - "message": "Limit views" + "message": "Batasi tampilan" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Tidak ada yang dapat melihat Kiriman ini setelah mencapai batasan.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ tampilan tersisa", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2370,22 +2391,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "searchSends": { - "message": "Pencarian Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Tambahkan Send", + "message": "Rincian Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "Teks" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Teks untuk dibagikan" }, "sendTypeFile": { "message": "Berkas" @@ -2395,23 +2408,16 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" - }, - "maxAccessCountReached": { - "message": "Jumlah akses maksimum tercapai", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + "message": "Sembunyikan teks secara bawaan" }, "expired": { "message": "Kedaluwarsa" }, - "pendingDeletion": { - "message": "Penghapusan menunggu keputusan" - }, "passwordProtected": { "message": "Dilindungi kata sandi" }, "copyLink": { - "message": "Copy link" + "message": "Salin tautan" }, "copySendLink": { "message": "Salin tautan Send", @@ -2449,42 +2455,23 @@ "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": "Apakah Anda yakin ingin menghapus Send ini selamanya?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Jenis Send apakah ini?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Nama yang bersahabat untuk menggambarkan Send ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "File yang ingin Anda kirim." - }, "deletionDate": { "message": "Tanggal Penghapusan" }, - "deletionDateDesc": { - "message": "Send akan dihapus secara permanen pada tanggal dan waktu yang ditentukan.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "Send akan dihapus selamanya pada tanggal ini.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "Tanggal habis tempo" }, - "expirationDateDesc": { - "message": "Jika disetel, akses ke Send ini akan berakhir pada tanggal dan waktu yang ditentukan.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 hari" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Kustom" }, - "maximumAccessCount": { - "message": "Hitungan Akses Maksimum" - }, - "maximumAccessCountDesc": { - "message": "Jika disetel, pengguna tidak dapat lagi mengakses Send ini setelah jumlah akses maksimum tercapai.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Secara opsional, minta kata sandi bagi pengguna untuk mengakses Send ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Catatan pribadi tentang Send ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Nonaktifkan Send ini sehingga tidak ada yang dapat mengaksesnya.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Salin tautan Send ini ke papan klip setelah disimpan.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teks yang ingin Anda kirim." - }, - "sendHideText": { - "message": "Sembunyikan teks Send ini secara default.", + "message": "Tambahkan kata sandi tidak wajib untuk penerima untuk mengakses Send ini.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "currentAccessCount": { - "message": "Hitungan Akses Saat Ini" - }, "createSend": { "message": "Buat Send Baru", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2557,15 +2511,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": "Send berhasil dibuat!", "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": "Send akan tersedia ke setiap orang yang memiliki tautan untuk 1 jam ke depan.", "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": "Send akan tersedia ke setiap orang yang memiliki tautan untuk $HOURS$ jam ke depan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2575,11 +2529,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "Send akan tersedia ke setiap orang yang memiliki tautan untuk 1 hari ke depan.", "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": "Send akan tersedia ke setiap orang yang memiliki tautan untuk $DAYS$ hari ke depan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2589,7 +2543,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Tautan Send disalin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2597,11 +2551,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": "Sembulkan ekstensi?", "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": "Untuk membuat sebuah berkas Send, Anda perlu menyembulkan ekstensi ke sebuah jendela baru.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2614,23 +2568,11 @@ "message": "Untuk memilih file menggunakan Safari, keluar ke jendela baru dengan mengklik spanduk ini." }, "popOut": { - "message": "Pop out" + "message": "Sembulkan" }, "sendFileCalloutHeader": { "message": "Sebelum kamu memulai" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Untuk menggunakan pemilih tanggal gaya kalender", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klik disini", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "untuk memunculkan jendela Anda.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Tanggal kedaluwarsa yang diberikan tidak valid." }, @@ -2646,14 +2588,8 @@ "dateParsingError": { "message": "Terjadi kesalahan saat menyimpan tanggal penghapusan dan kedaluwarsa Anda." }, - "hideEmail": { - "message": "Sembunyikan alamat surel dari penerima." - }, "hideYourEmail": { - "message": "Hide your email address from viewers." - }, - "sendOptionsPolicyInEffect": { - "message": "Satu atau lebih kebijakan organisasi mempengaruhi pengaturan feature Send anda." + "message": "Sembunyikan alamat surel Anda dari penonton." }, "passwordPrompt": { "message": "Master password ditanyakan kembali" @@ -2668,7 +2604,7 @@ "message": "Verifikasi Email Diperlukan" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Surel telah diverifikasi" }, "emailVerificationRequiredDesc": { "message": "Anda harus memverifikasi email Anda untuk menggunakan fitur ini. Anda dapat memverifikasi email Anda di brankas web." @@ -2683,10 +2619,10 @@ "message": "Kata Sandi Utama Anda baru-baru ini diubah oleh administrator organisasi Anda. Untuk mengakses brankas tersebut, Anda diharuskan memperbarui Kata Sandi Utama Anda sekarang. Jika Anda melanjutkan, Anda akan keluar dari sesi saat ini, yang mana mengharuskan Anda untuk login kembali. Sesi yang aktif di perangkat lain akan tetap aktif selama satu jam kedepan." }, "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": "Kata sandi utama Anda tidak memenuhi satu atau lebih dari kebijakan organisasi Anda. Untuk dapat mengakses brankas, Anda harus memperbarui kata sandi utama Anda sekarang. Melanjutkan akan mengeluarkan Anda dari sesi saat ini, memerlukan Anda untuk masuk kembali. Sesi aktif pada perangkat lainnya dapat tetap aktif hingga satu jam." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Organisasi Anda telah mematikan enkripsi perangkat terpercaya. Mohon mengatur kata sandi utama untuk mengakses brankas Anda." }, "resetPasswordPolicyAutoEnroll": { "message": "Pendaftaran Otomatis" @@ -2698,19 +2634,19 @@ "message": "Pilih Folder..." }, "noFoldersFound": { - "message": "No folders found", + "message": "Tidak ada folder yang ditemukan", "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": "Perizinan organisasi Anda telah diperbarui, memerlukan Anda untuk mengatur kata sandi utama.", "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": "Organisasi Anda memerlukan Anda untuk mengatur sebuah kata sandi utama.", "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": "dari $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2719,7 +2655,7 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "Memerlukan verifikasi", "description": "Default title for the user verification dialog." }, "hours": { @@ -2729,7 +2665,7 @@ "message": "Menit" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Persyaratan kebijakan perusahaan telah diterapkan ke pilihan batas waktu Anda" }, "vaultTimeoutPolicyInEffect": { "message": "Kebijakan organisasi Anda memengaruhi waktu tunggu brankas Anda. Batas maksimal Waktu Tunggu Brankas yang diizinkan adalah $HOURS$ jam dan $MINUTES$ menit", @@ -2745,7 +2681,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Maksimal $HOURS$ jam dan $MINUTES$ menit.", "placeholders": { "hours": { "content": "$1", @@ -2758,7 +2694,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Batas waktu melebihi dari batasan yang telah ditetapkan oleh organisasi Anda: maksimal $HOURS$ jam dan $MINUTES$ menit", "placeholders": { "hours": { "content": "$1", @@ -2771,7 +2707,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": "Kebijakan organisasi Anda mempengaruhi batas waktu brankas Anda. Batas waktu maksimum yang dibolehkan adalah $HOURS$ jam dan $MINUTES$ menit. Batas waktu tindakan brankas Anda diatur ke $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2788,7 +2724,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "Kebijakan organisasi Anda telah mengatur batas waktu tindakan brankas Anda ke $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2797,7 +2733,7 @@ } }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "Batas waktu brankas Anda melebihi batasan yang telah ditetapkan oleh organisasi Anda." }, "vaultExportDisabled": { "message": "Ekspor Brankas Dinonaktifkan" @@ -2812,7 +2748,7 @@ "message": "Tidak ada pengidentifikasi unik yang ditemukan." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "message": "$ORGANIZATION$ menggunakan SSO dengan server kunci yang dihosting sendiri. Kata sandi utama tidak lagi diperlukan untuk masuk untuk anggota organisasi ini.", "placeholders": { "organization": { "content": "$1", @@ -2836,16 +2772,16 @@ "message": "Anda telah keluar dari organisasi." }, "toggleCharacterCount": { - "message": "Toggle character count" + "message": "Saklar hitung karakter" }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "Sesi Anda telah berakhir. Harap kembali dan coba masuk lagi." }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "Mengekspor brankas individu" }, "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": "Hanya benda-benda brankas perorangan yang terkait dengan $EMAIL$ yang akan diekspor. Benda-benda brankas organisasi tidak akan disertakan. Hanya informasi benda brankas yang akan diekspor dan tidak menyertakan lampiran yang terkait.", "placeholders": { "email": { "content": "$1", @@ -2854,10 +2790,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "Mengekspor brankas organisasi" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Hanya brankas organisasi yang terkait dengan $ORGANIZATION$ yang akan diekspor. Benda-benda di brankas perorangan atau organisasi lainnya tidak akan disertakan.", "placeholders": { "organization": { "content": "$1", @@ -2868,17 +2804,28 @@ "error": { "message": "Galat" }, - "regenerateUsername": { - "message": "Buat nama pengguna baru" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Buat nama pengguna baru" }, "generateEmail": { - "message": "Generate email" + "message": "Buat email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Nilai harus ada di antara $MIN$ dan $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,21 +2838,38 @@ } } }, - "usernameType": { - "message": "Jenis nama pengguna" + "passwordLengthRecommendationHint": { + "message": " Gunakan $RECOMMENDED$ karakter atau lebih untuk menghasilkan kata sandi yang kuat.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Gunakan $RECOMMENDED$ kata atau lebih untuk menghasilkan frasa sandi yang kuat.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "Surel dengan alamat plus", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "Gunakan kemampuan sub-addressing penyedia surel Anda." }, "catchallEmail": { - "message": "Catch-all email" + "message": "Surel tangkap-semua" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "Gunakan pengaturan kotak masuk tangkap-semua milik domain Anda." }, "random": { "message": "Acak" @@ -2916,31 +2880,25 @@ "websiteName": { "message": "Nama situs web" }, - "whatWouldYouLikeToGenerate": { - "message": "Apa yang ingin Anda buat?" - }, - "passwordType": { - "message": "Jenis kata sandi" - }, "service": { - "message": "Service" + "message": "Layanan" }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Alias surel yang diteruskan" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Buat alias surel dengan layanan penerusan eksternal." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domain surel", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Pilih domain yang didukung oleh layanan terpilih", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "Galat $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2954,11 +2912,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Dibuat oleh Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Situs web: $WEBSITE$. Dibuat oleh Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2968,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Token API $SERVICENAME$ tidak valid", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2978,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Token API $SERVICENAME$ tidak valid: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2992,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Gagal mendapatkan akun ID surel bertopeng dari $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3002,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Domain $SERVICENAME$ tidak valid.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3012,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "URL $SERVICENAME$ tidak valid.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3022,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Terjadi galat yang tidak diketahui dari $SERVICENAME$.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3032,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Penerus tidak diketahui: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3042,29 +3000,29 @@ } }, "hostname": { - "message": "Hostname", + "message": "Nama host", "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "Token Akses API" }, "apiKey": { - "message": "API Key" + "message": "Kunci API" }, "ssoKeyConnectorError": { - "message": "Key connector error: make sure key connector is available and working correctly." + "message": "Galat kunci penyambung: pastikan kunci penyambung tersedia dan bekerja dengan benar." }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "Langganan premium diperlukan" }, "organizationIsDisabled": { - "message": "Organization suspended." + "message": "Organisasi ditangguhkan." }, "disabledOrganizationFilterError": { - "message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "Benda-benda di Organisasi yang ditangguhkan tidak dapat diakses. Hubungi pemilik Organisasi Anda untuk mendapatkan panduan." }, "loggingInTo": { - "message": "Logging in to $DOMAIN$", + "message": "Sedang masuk ke $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3073,25 +3031,25 @@ } }, "settingsEdited": { - "message": "Settings have been edited" + "message": "Pengaturan telah disunting" }, "environmentEditedClick": { - "message": "Click here" + "message": "Tekan di sini" }, "environmentEditedReset": { - "message": "to reset to pre-configured settings" + "message": "untuk mengatur ulang ke pengaturan awal" }, "serverVersion": { - "message": "Server version" + "message": "Versi server" }, "selfHostedServer": { - "message": "self-hosted" + "message": "dihosting mandiri" }, "thirdParty": { - "message": "Third-party" + "message": "Pihak ketiga" }, "thirdPartyServerMessage": { - "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", + "message": "Tersambung ke penerapan server pihak ketiga, $SERVERNAME$. Mohon pastikan bug menggunakan server resmi, atau laporkan mereka ke server pihak ketiga.", "placeholders": { "servername": { "content": "$1", @@ -3100,7 +3058,7 @@ } }, "lastSeenOn": { - "message": "last seen on: $DATE$", + "message": "terakhir terlihat pada: $DATE$", "placeholders": { "date": { "content": "$1", @@ -3109,67 +3067,82 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Masuk dengan kata sandi utama" }, "loggingInAs": { - "message": "Logging in as" + "message": "Masuk sebagai" }, "notYou": { - "message": "Not you?" + "message": "Bukan Anda?" }, "newAroundHere": { - "message": "New around here?" + "message": "Baru di sini?" }, "rememberEmail": { "message": "Ingat email" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Masuk dengan perangkat" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "Masuk dengan perangkat harus diatur di pengaturan aplikasi Bitwarden. Perlu pilihan lainnya?" }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "Frasa sidik jari" }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device." + "message": "Pastikan brankas Anda terbuka dan frasa sidik jari cocok pada perangkat lainnya." }, "resendNotification": { - "message": "Resend notification" + "message": "Kirim ulang pemberitahuan" + }, + "viewAllLogInOptions": { + "message": "Lihat semua pilihan masuk" }, - "viewAllLoginOptions": { - "message": "View all log in options" + "viewAllLoginOptionsV1": { + "message": "Lihat semua pilihan masuk" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "Sebuah pemberitahuan dikirim ke perangkat Anda." + }, + "aNotificationWasSentToYourDevice": { + "message": "Sebuah pemberitahuan telah dikirim ke perangkat Anda" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Pastikan akun Anda terbuka dan frasa sidik jari cocok pada perangkat lainnya" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Anda akan diberitahu setelah permintaan disetujui" + }, + "needAnotherOptionV1": { + "message": "Perlu pilihan lainnya?" }, "loginInitiated": { - "message": "Login initiated" + "message": "Memulai login" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "Kata Sandi Utama yang Terpapar" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "Kata sandi ditemukan di pelanggaran data. Gunakan kata sandi unik untuk melindungi akun Anda. Apakah Anda yakin ingin menggunakan kata sandi yang terpapar?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "Kata Sandi Utama yang Lemah dan Terpapar" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "Kata sandi lemah dikenali dan ditemukan di pelanggaran data. Gunakan kata sandi unik untuk melindungi akun Anda. Apakah Anda yakin ingin menggunakan kata sandi ini?" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "Periksa pelanggaran data yang diketahui untuk kata sandi ini" }, "important": { - "message": "Important:" + "message": "Penting:" }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Kata sandi utama Anda tidak dapat dipulihkan jika Anda melupakannya!" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "Minimal $LENGTH$ karakter", "placeholders": { "length": { "content": "$1", @@ -3178,13 +3151,13 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Your organization policies have turned on autofill on page load." + "message": "Kebijakan organisasi Anda telah menyalakan isi otomatis ketika halaman dimuat." }, "howToAutofill": { - "message": "How to autofill" + "message": "Bagaimana cara mengisi otomatis" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Pilih sebuah benda dari layar ini, gunakan pintasan $COMMAND$, atau jelajahi pilihan lainnya di pengaturan.", "placeholders": { "command": { "content": "$1", @@ -3193,31 +3166,31 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Pilih sebuah benda dari layar ini, atau jelajahi pilihan lainnya di pengaturan." }, "gotIt": { - "message": "Got it" + "message": "Dimengerti" }, "autofillSettings": { - "message": "Autofill settings" + "message": "Pengaturan isi otomatis" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "Pintasan isi otomatis" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "Ubah pintasan" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Kelola pintasan" }, "autofillShortcut": { - "message": "Autofill keyboard shortcut" + "message": "Pintasan papan ketik isi otomatis" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "Pintasan isi otomatis login belum diatur. Ubah ini di pengaturan peramban." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "Pintasan isi otomatis login adalah $COMMAND$. Kelola semua pintasan di pengaturan peramban.", "placeholders": { "command": { "content": "$1", @@ -3226,7 +3199,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Default autofill shortcut: $COMMAND$.", + "message": "Pintasan isi otomatis bawaan: $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -3235,106 +3208,118 @@ } }, "opensInANewWindow": { - "message": "Opens in a new window" + "message": "Buka di jendela baru" + }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Ingat perangkat ini untuk membuat login berikutnya lebih lancar" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Persetujuan perangkat diperlukan. Pilih sebuah pilihan persetujuan berikut:" + }, + "deviceApprovalRequiredV2": { + "message": "Persetujuan perangkat diperlukan" + }, + "selectAnApprovalOptionBelow": { + "message": "Pilih sebuah pilihan persetujuan berikut" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Ingat perangkat ini" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Batalkan centang jika menggunakan perangkat umum" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Setujui dari perangkat lain milik Anda" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Minta persetujuan admin" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Setujui dengan kata sandi utama" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Pengenal SSO organisasi diperlukan." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Membuat akun pada" }, "checkYourEmail": { - "message": "Check your email" + "message": "Periksa surel Anda" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Ikuti tautan pada surel yang telah dikirim" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "dan lanjutkan membuat akun Anda." }, "noEmail": { - "message": "No email?" + "message": "Tidak punya surel?" }, "goBack": { - "message": "Go back" + "message": "Kembali" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "untuk menyunting alamat surel Anda." }, "eu": { "message": "EU", "description": "European Union" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Akses ditolak. Anda tidak mempunyai izin untuk melihat halaman ini." }, "general": { - "message": "General" + "message": "Umum" }, "display": { - "message": "Display" + "message": "Tampilan" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Akun berhasil dibuat!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Persetujuan admin telah diminta" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Permintaan Anda telah dikirim ke admin Anda." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Anda akan diberitahu setelah disetujui." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Kesulitan masuk?" }, "loginApproved": { - "message": "Login approved" + "message": "Login disetujui" }, "userEmailMissing": { - "message": "User email missing" + "message": "Surel pengguna hilang" + }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Surel pengguna yang aktif tidak ditemukan. Mengeluarkan Anda." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Perangkat dipercaya" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "Tidak ada Send yang aktif", "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.", + "message": "Gunakan Send untuk membagikan informasi terenkripsi secara aman dengan siapapun.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "Masukan ini harus diisi." }, "required": { - "message": "required" + "message": "wajib diisi" }, "search": { - "message": "Search" + "message": "Cari" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Masukan sekurang-kurangnya $COUNT$ karakter.", "placeholders": { "count": { "content": "$1", @@ -3343,7 +3328,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Masukan tidak boleh melebihi $COUNT$ karakter.", "placeholders": { "count": { "content": "$1", @@ -3352,7 +3337,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Karakter berikut tidak diperbolehkan: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3361,7 +3346,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Nilai masukan sekurang-kurangnya $MIN$.", "placeholders": { "min": { "content": "$1", @@ -3370,7 +3355,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Nilai masukan tidak boleh melebihi $MAX$.", "placeholders": { "max": { "content": "$1", @@ -3379,17 +3364,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 atau lebih surel tidak valid" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Masukan tidak boleh berisi hanya spasi kosong.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Masukan bukan sebuah alamat surel." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ bidang di atas memerlukan perhatian Anda.", "placeholders": { "count": { "content": "$1", @@ -3398,10 +3383,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 bidang memerlukan perhatian Anda." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ bidang memerlukan perhatian Anda.", "placeholders": { "count": { "content": "$1", @@ -3410,22 +3395,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Pilih --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Ketik untuk menyaring --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Mengambil pilihan..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Tidak ada benda yang ditemukan" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Bersihkan semua" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ $QUANTITY$ lagi", "placeholders": { "quantity": { "content": "$1", @@ -3437,165 +3422,173 @@ "message": "Submenu" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "Saklar lipat", "description": "Toggling an expand/collapse state." }, "filelessImport": { - "message": "Import your data to Bitwarden?", + "message": "Impor data Anda ke Bitwarden?", "description": "Default notification title for triggering a fileless import." }, "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", + "message": "Lindungi data LastPass Anda dan impor ke Bitwarden?", "description": "LastPass specific notification title for triggering a fileless import." }, "lpCancelFilelessImport": { - "message": "Save as unencrypted file", + "message": "Simpan sebagai berkas yang tidak dienkripsi", "description": "LastPass specific notification button text for cancelling a fileless import." }, "startFilelessImport": { - "message": "Import to Bitwarden", + "message": "Impor ke Bitwarden", "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importing...", + "message": "Mengimpor...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "Data successfully imported!", + "message": "Data berhasil diimpor!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Gagal mengimpor. Periksa konsol untuk rinciannya.", "description": "Notification message for when an import has failed." }, "importNetworkError": { - "message": "Network error encountered during import.", + "message": "Kesalahan jaringan ditemui ketika mengimpor.", "description": "Notification message for when an import has failed due to a network error." }, "aliasDomain": { - "message": "Alias domain" + "message": "Domain alias" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be autofilled on page load. Autofill on page load turned off.", + "message": "Benda dengan meminta ulang kata sandi utama tidak dapat diisikan otomatis ketika halaman dimuat. Isi otomatis ketika halaman dimuat dimatikan.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "Isi otomatis ketika halaman dimuat telah diatur untuk menggunakan pengaturan bawaan.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "Matikan minta ulang kata sandi utama untuk menyunting kolom ini", "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": "Saklar bilah isi navigasi" }, "skipToContent": { - "message": "Skip to content" + "message": "Loncat ke konten" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "Tombol menu isi otomatis Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "Saklar menu isi otomatis Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "Menu isi otomatis Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "Buka akun Anda untuk melihat login yang cocok", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "Buka akun Anda untuk melihat saran isi otomatis", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "Buka akun", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "Buka akun Anda, membukanya di jendela baru", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Kode Verifikasi Kata Sandi Sekali-Waktu Berbasis Waktu", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Waktu tersisa sebelum TOTP sekarang kadaluwarsa", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "Isi tanda pengenal untuk", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "Nama pengguna sebagian", "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": { - "message": "No items to show", + "message": "Tidak ada benda untuk ditampilkan", "description": "Text to show in overlay if there are no matching items" }, "newItem": { - "message": "New item", + "message": "Benda baru", "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Add new vault item", + "message": "Tambah benda brankas baru", "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "Login baru", "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": "Tambah baru untuk benda login, membukanya di jendela baru", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Kartu baru", "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": "Tambah brankas baru untuk benda kartu, membukanya di jendela baru", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "Pengenal baru", "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": "Tambah brankas baru untuk benda pengenal, membukanya di jendela baru", "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": "Menu isi otomatis Bitwarden tersedia. Tekan tombol panah ke bawah untuk memilih.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "Nyalakan" }, "ignore": { - "message": "Ignore" + "message": "Abaikan" }, "importData": { - "message": "Import data", + "message": "Impor data", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Kesalahan impor" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "Ada masalah dengan data yang Anda coba impor. Harap selesaikan kesalahan yang tercantum di bawah ini pada berkas sumber Anda dan coba lagi." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "Selesaikan masalah berikut dan coba lagi." }, "description": { - "message": "Description" + "message": "Keterangan" }, "importSuccess": { - "message": "Data successfully imported" + "message": "Data berhasil diimpor" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Sejumlah $AMOUNT$ benda telah diimpor.", "placeholders": { "amount": { "content": "$1", @@ -3604,46 +3597,46 @@ } }, "tryAgain": { - "message": "Try again" + "message": "Coba lagi" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "Diperlukan verifikasi untuk tindakan ini. Atur PIN untuk melanjutkan." }, "setPin": { - "message": "Set PIN" + "message": "Atur PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Verifikasi dengan biometrik" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Menunggu konfirmasi" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Tidak dapat melengkapi biometrik." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "Perlu cara lain?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Gunakan kata sandi utama" }, "usePin": { - "message": "Use PIN" + "message": "Gunakan PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Gunakan biometrik" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Masukkan kode verifikasi yang dikirim ke surel Anda." }, "resendCode": { - "message": "Resend code" + "message": "Kirim ulang kode" }, "total": { - "message": "Total" + "message": "Jumlah" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "Anda mengimpor data ke $ORGANIZATION$. Data Anda dapat dibagikan dengan anggota organisasi ini. Apakah Anda ingin melanjutkan?", "placeholders": { "organization": { "content": "$1", @@ -3652,49 +3645,49 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Gagal menyambungkan dengan layanan Duo. Gunakan cara masuk dua-langkah lainnya atau hubungi Duo untuk mendapatkan panduan." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Luncurkan Duo dan ikuti langkah-langkah untuk menyelesaikan masuk." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "Login dua-langkah Duo diperlukan untuk akun Anda." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "Sembulkan ekstensi untuk melengkapi login." }, "popoutExtension": { - "message": "Popout extension" + "message": "Sembulkan ekstensi" }, "launchDuo": { - "message": "Launch Duo" + "message": "Luncurkan Duo" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "Data tidak diformat dengan benar. Harap periksa berkas impor Anda dan coba lagi." }, "importNothingError": { - "message": "Nothing was imported." + "message": "Tidak ada yang diimpor." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "Tidak dapat mendekripsi berkas yang diekspor. Kunci enkripsi Anda tidak cocok dengan kunci enkripsi yang digunakan untuk mengekspor data tersebut." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Kata sandi berkas tidak valid, harap menggunakan kata sandi yang Anda masukkan saat Anda membuat berkas ekspor." }, "destination": { - "message": "Destination" + "message": "Tujuan" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "Pelajari tentang pilihan impor Anda" }, "selectImportFolder": { - "message": "Select a folder" + "message": "Pilih folder" }, "selectImportCollection": { - "message": "Select a collection" + "message": "Pilih koleksi" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "Pilih pilihan ini jika Anda ingin isi dari berkas yang diimpor dipindah ke $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": { @@ -3704,25 +3697,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "Berkas berisi benda-benda yang belum ditetapkan." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "Pilih format untuk berkas yang diimpor" }, "selectImportFile": { - "message": "Select the import file" + "message": "Pilih berkas yang akan diimpor" }, "chooseFile": { - "message": "Choose File" + "message": "Pilih Berkas" }, "noFileChosen": { - "message": "No file chosen" + "message": "Tidak ada berkas yang dipilih" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "atau salin/tempel isi berkas yang diimpor" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "Petunjuk $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3732,197 +3725,200 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Konfirmasi impor brankas" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Berkas ini dilindungi oleh kata sandi. Masukkan kata sandi berkas untuk mengimpor data." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Konfirmasi kata sandi berkas" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Data brankas berhasil diekspor" }, "typePasskey": { - "message": "Passkey" + "message": "Kunci sandi" }, "accessing": { - "message": "Accessing" + "message": "Sedang mengakses" + }, + "loggedInExclamation": { + "message": "Sudah masuk!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Kunci sandi tidak akan disalin" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "Kunci sandi tidak akan disalin ke benda yang digandakan. Apakah Anda ingin melanjutkan menggandakan benda ini?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { - "message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password." + "message": "Verifikasi diperlukan oleh situs yang menyelenggarakan. Fitur ini belum diterapkan untuk akun tanpa kata sandi utama." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Masuk dengan kunci sandi?" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "Kunci sandi sudah ada untuk aplikasi ini." }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "Tidak ada kunci sandi yang ditemukan untuk aplikasi ini." }, "noMatchingPasskeyLogin": { - "message": "You do not have a matching login for this site." + "message": "Anda tidak memiliki login yang cocok untuk situs ini." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Tidak ada login yang cocok untuk situs ini" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Cari atau simpan kunci sandi sebagai login baru" }, "confirm": { - "message": "Confirm" + "message": "Konfirmasi" }, "savePasskey": { - "message": "Save passkey" + "message": "Simpan kunci sandi" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Simpan kunci sandi sebagai login baru" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Pilih sebuah login untuk menyimpan kunci sandi ini" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Pilih sebuah kunci sandi untuk masuk" }, "passkeyItem": { - "message": "Passkey Item" + "message": "Benda kunci sandi" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "Timpa kunci sandi?" }, "overwritePasskeyAlert": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "Benda ini telah memiliki kunci sandi. Apakah Anda yakin ingin menimpa kunci sandi yang sekarang?" }, "featureNotSupported": { - "message": "Feature not yet supported" + "message": "Kemampuan belum didukung" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "Otentikasi diperlukan untuk menggunakan kunci sandi. Verifikasikan diri Anda untuk melanjutkan." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "Otentikasi multifaktor dibatalkan" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "Tidak ada data LastPass yang ditemukan" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "Nama pengguna atau kata sandi salah" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Kata sandi salah" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Kode salah" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN salah" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "Otentikasi multifaktor gagal" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "Sertakan folder yang dibagikan" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "Surel LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "Mengimpor akun Anda..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "Otentikasi multifaktor LastPass diperlukan" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "Masukkan kode sandi sekali-waktu dari aplikasi otentikator Anda" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "Setujui permintaan masuk di aplikasi otentikasi Anda atau masukkan kode sandi sekali-waktu." }, "passcode": { - "message": "Passcode" + "message": "Kode sandi" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "Kata sandi utama LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "Otentikasi LastPass diperlukan" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "Menunggu otentikasi SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "Harap lanjutkan masuk menggunakan tanda pengenal perusahaan Anda." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "Lihat petunjuk lengkap pada situs bantuan kami di", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "Impor langsung dari LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "Impor dari CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "Coba lagi atau cari surel dari LastPass untuk memverifikasi bahwa ini adalah Anda." }, "collection": { - "message": "Collection" + "message": "Koleksi" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Masukkan YubiKey yang ditautkan dengan akun LastPass Anda ke port USB komputer Anda, kemudian sentuh tombolnya." }, "switchAccount": { - "message": "Switch account" + "message": "Ganti akun" }, "switchAccounts": { - "message": "Switch accounts" + "message": "Ganti akun" }, "switchToAccount": { - "message": "Switch to account" + "message": "Ganti ke akun" }, "activeAccount": { - "message": "Active account" + "message": "Akun aktif" }, "availableAccounts": { - "message": "Available accounts" + "message": "Akun yang tersedia" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "Batas akun tercapai. Keluar dari akun untuk menambahkan akun lain." }, "active": { - "message": "active" + "message": "aktif" }, "locked": { - "message": "locked" + "message": "terkunci" }, "unlocked": { - "message": "unlocked" + "message": "terbuka" }, "server": { "message": "server" }, "hostedAt": { - "message": "hosted at" + "message": "dihost di" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Gunakan perangkat Anda atau kunci perangkat keras" }, "justOnce": { - "message": "Just once" + "message": "Hanya sekali" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "Selalu untuk situs ini" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ ditambahkan ke domain yang dikecualikan.", "placeholders": { "domain": { "content": "$1", @@ -3931,103 +3927,103 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "Format umum", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Lanjutkan ke pengaturan peramban?", "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": "Lanjutkan ke Pusat Bantuan?", "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": "Ganti pengaturan isi otomatis dan pengelolaan kata sandi peramban Anda.", "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": "Anda dapat melihat dan mengatur pintasan ekstensi di pengaturan peramban Anda.", "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": "Ganti pengaturan isi otomatis dan pengelolaan kata sandi peramban Anda.", "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": "Anda dapat melihat dan mengatur pintasan ekstensi di pengaturan peramban Anda.", "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": "Jadikan Bitwarden sebagai pengelola kata sandi bawaan Anda?", "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": "Mengabaikan pilihan ini dapat mengakibatkan perseteruan antara saran isi otomatis Bitwarden dengan peramban Anda.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Jadikan Bitwarden sebagai pengelola kata sandi bawaan Anda", "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": "Tidak dapat mengatur Bitwarden sebagai pengelola kata sandi bawaan", "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": "Anda harus mengizinkan perizinan privasi peramban kepada Bitwarden untuk mengaturnya sebagai pengelola kata sandi bawaan.", "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": "Jadikan bawaan", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Kredensial berhasil disimpan!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Kata sandi telah disimpan!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Kredensial berhasil diperbarui!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Kata sandi telah diperbarui!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Gagal menyimpan kredensial. Periksa konsol untuk rinciannya.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Berhasil" }, "removePasskey": { - "message": "Remove passkey" + "message": "Hapus kunci sandi" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Kunci sandi dihapus" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Saran isi otomatis" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "Simpan benda login untuk situs ini ke isi otomatis" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Brankas Anda kosong" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Tidak ada benda yang cocok dengan pencarian Anda" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Bersihkan penyaringan atau coba cari kata lainnya" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "Menyalin info - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4037,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "Menyalin Catatan - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4047,7 +4043,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Pilihan lainnya, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4057,7 +4053,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Pilihan lainnya - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "LIhat benda - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4077,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "Isi otomatis - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4087,40 +4083,40 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "Tidak ada nilai untuk disalin" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Menempatkan ke koleksi" }, "copyEmail": { - "message": "Copy email" + "message": "Salin surel" }, "copyPhone": { - "message": "Copy phone" + "message": "Salin nomor telepon" }, "copyAddress": { - "message": "Copy address" + "message": "Salin alamat" }, "adminConsole": { - "message": "Admin Console" + "message": "Konsol Admin" }, "accountSecurity": { - "message": "Account security" + "message": "Keamanan akun" }, "notifications": { - "message": "Notifications" + "message": "Pemberitahuan" }, "appearance": { - "message": "Appearance" + "message": "Tampilan" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Gagal menetapkan ke koleksi yang dituju." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Gagal menetapkan ke folder yang dituju." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Lihat benda-benda di $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4130,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Kembali ke $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4140,10 +4136,10 @@ } }, "new": { - "message": "New" + "message": "Baru" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Buang $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4153,16 +4149,16 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Benda-benda tanpa folder" }, "itemDetails": { - "message": "Item details" + "message": "Rincian benda" }, "itemName": { - "message": "Item name" + "message": "Nama benda" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Anda tidak dapat menghapus koleksi dengan izin hanya lihat: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -4171,47 +4167,47 @@ } }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "Organisasi dinonaktifkan" }, "owner": { - "message": "Owner" + "message": "Pemilik" }, "selfOwnershipLabel": { - "message": "You", + "message": "Anda", "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": "Benda-benda di organisasi yang dinonaktifkan tidak dapat diakses. Hubungi pemilik organisasi Anda untuk mendapatkan panduan." }, "additionalInformation": { - "message": "Additional information" + "message": "Informasi tambahan" }, "itemHistory": { - "message": "Item history" + "message": "Riwayat benda" }, "lastEdited": { - "message": "Last edited" + "message": "Terakhir disunting" }, "ownerYou": { - "message": "Owner: You" + "message": "Pemilik: Anda" }, "linked": { - "message": "Linked" + "message": "Terkait" }, "copySuccessful": { - "message": "Copy Successful" + "message": "Berhasil Disalin" }, "upload": { - "message": "Upload" + "message": "Unggah" }, "addAttachment": { - "message": "Add attachment" + "message": "Tambahkan lampiran" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Ukuran berkas maksimal adalah 500 MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "Hapus lampiran $NAME$", "placeholders": { "name": { "content": "$1", @@ -4220,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "Unduh $NAME$", "placeholders": { "name": { "content": "$1", @@ -4229,28 +4225,43 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Apakah Anda yakin ingin menghapus lampiran ini selamanya?" }, "premium": { "message": "Premium" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "Organisasi gratis tidak dapat menggunakan lampiran" }, "filters": { - "message": "Filters" + "message": "Penyaring" + }, + "filterVault": { + "message": "Penyaring brankas" + }, + "filterApplied": { + "message": "Satu saringan diterapkan" + }, + "filterAppliedPlural": { + "message": "$COUNT$ saringan diterapkan", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } }, "personalDetails": { - "message": "Personal details" + "message": "Rincian pribadi" }, "identification": { - "message": "Identification" + "message": "Pengenalan" }, "contactInfo": { - "message": "Contact info" + "message": "Info kontak" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Unduh - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4259,23 +4270,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "nomor kartu berakhiran", "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": "Kredensial login" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Kunci Otentikator" }, "autofillOptions": { - "message": "Autofill options" + "message": "Pilihan isi otomatis" }, "websiteUri": { - "message": "Website (URI)" + "message": "Situs web (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "$COUNT$ Situs web (URI)", "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": { @@ -4285,16 +4296,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Situs web ditambahkan" }, "addWebsite": { - "message": "Add website" + "message": "Tambah situs web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Hapus situs web" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Bawaan ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4304,7 +4315,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Tampilkan deteksi kecocokan $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4313,7 +4324,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Sembunyikan deteksi kecocokan $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4322,19 +4333,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Isi otomatis ketika halaman dimuat?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Kartu kadaluwarsa" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Jika Anda telah memperpanjangnya, perbarui informasi kartu" }, "cardDetails": { - "message": "Card details" + "message": "Rincian kartu" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Rincian $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -4343,43 +4354,43 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "Nyalakan animasi" }, "showAnimations": { - "message": "Show animations" + "message": "Tampilkan animasi" }, "addAccount": { - "message": "Add account" + "message": "Tambah akun" }, "loading": { - "message": "Loading" + "message": "Memuat" }, "data": { "message": "Data" }, "passkeys": { - "message": "Passkeys", + "message": "Kunci sandi", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Kata Sandi", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Masuk dengan kunci sandi", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "Terapkan" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "Hanya anggota organisasi dengan akses ke koleksi berikut yang dapat melihat isinya." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Hanya anggota organisasi dengan akses ke koleksi berikut yang dapat melihat isinya." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "Anda telah memilih $TOTAL_COUNT$ benda. Anda tidak dapat memperbarui $READONLY_COUNT$ dari benda karena Anda tidak memiliki izin untuk menyunting.", "placeholders": { "total_count": { "content": "$1", @@ -4391,37 +4402,37 @@ } }, "addField": { - "message": "Add field" + "message": "Tambahkan bidang" }, "add": { - "message": "Add" + "message": "Tambah" }, "fieldType": { - "message": "Field type" + "message": "Jenis bidang" }, "fieldLabel": { - "message": "Field label" + "message": "Label bidang" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Gunakan bidang teks untuk data seperti pertanyaan keamanan" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Gunakan bidang tersembunyi untuk data sensitif seperti kata sandi" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Gunakan kotak centang jika Anda ingin mengisi sebuah kotak centang di formullir, seperti mengingat surel" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "Gunakan bidang tertaut ketika Anda mengalami masalah pengisian otomatis untuk situs web tertentu." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "Masukkan id, name, aria-label, atau placeholder html dari bidang." }, "editField": { - "message": "Edit field" + "message": "Sunting bidang" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Sunting $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Hapus $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ ditambahkan", "placeholders": { "label": { "content": "$1", @@ -4448,7 +4459,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "Urutkan $LABEL$. Gunakan tombol panah untuk memindahkan benda ke atas atau ke bawah.", "placeholders": { "label": { "content": "$1", @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4652,140 +4717,191 @@ "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Tanda gelombang", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Tanda petik terbalik", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Tanda seru", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Tanda pada", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Tanda pagar", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Tanda dolar", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Tanda persen", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Tanda sisipan", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Tanda dan", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Tanda bintang", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Tanda kurung kiri", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Tanda kurung kanan", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Garis bawah", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Tanda penghubung", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Tanda tambah", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Tanda sama dengan", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Kurung kurawal kiri", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Kurung kurawal kanan", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Tanda kurung siku kiri", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Tanda kurung siku kanan", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Garis tegak lurus", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Garis miring terbalik", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Tanda titik dua", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Tanda titik koma", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Tanda petik ganda", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Tanda petik tunggal", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Tanda kurang dari", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Tanda lebih besar dari", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Tanda koma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Tanda titik", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Tanda tanya", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Tanda garis miring ke depan", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Huruf kecil" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Huruf kapital" }, "generatedPassword": { - "message": "Generated password" + "message": "Kata sandi yang dihasilkan" + }, + "compactMode": { + "message": "Mode ringkas" + }, + "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": "Lebar ekstensi" + }, + "wide": { + "message": "Lebar" + }, + "extraWide": { + "message": "Ekstra lebar" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 0023b9755a5..04e2c4ee64f 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -20,7 +20,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "Nuovo a Bitwarden?" + "message": "Nuovo in Bitwarden?" }, "logInWithPasskey": { "message": "Accedi con passkey" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copia numero licenza" }, + "copyPrivateKey": { + "message": "Copia chiave privata" + }, + "copyPublicKey": { + "message": "Copia chiave pubblica" + }, + "copyFingerprint": { + "message": "Copia impronta" + }, "copyCustomField": { "message": "Copia $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Riempi automaticamente identità" }, + "fillVerificationCode": { + "message": "Riempi codice di verifica" + }, + "fillVerificationCodeAria": { + "message": "Riempi Codice di Verifica", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Genera password e copiala" }, @@ -223,7 +239,7 @@ "message": "Aggiungi elemento" }, "accountEmail": { - "message": "Account email" + "message": "Email dell'account" }, "requestHint": { "message": "Richiedi suggerimento" @@ -427,7 +443,7 @@ "message": "Genera password" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera passphrase" }, "regeneratePassword": { "message": "Rigenera password" @@ -438,9 +454,6 @@ "length": { "message": "Lunghezza" }, - "passwordMinLength": { - "message": "Lunghezza minima della password" - }, "uppercase": { "message": "Maiuscole (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,16 +525,12 @@ "minSpecial": { "message": "Minimo caratteri speciali" }, - "avoidAmbChar": { - "message": "Evita caratteri ambigui", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Evita caratteri ambigui", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "I requisiti di politica aziendale sono stati applicati alle opzioni del generatore.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -567,7 +576,7 @@ "message": "Note" }, "privateNote": { - "message": "Private note" + "message": "Nota privata" }, "note": { "message": "Nota" @@ -591,7 +600,7 @@ "message": "Avvia il sito web" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Apri sito web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -624,7 +633,7 @@ "message": "Timeout della sessione" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "Timeout cassaforte" }, "otherOptions": { "message": "Altre opzioni" @@ -632,9 +641,6 @@ "rateExtension": { "message": "Valuta l'estensione" }, - "rateExtensionDesc": { - "message": "Aiutaci lasciando una buona recensione!" - }, "browserNotSupportClipboard": { "message": "Il tuo browser non supporta copiare dagli appunti. Copialo manualmente." }, @@ -645,13 +651,13 @@ "message": "La tua cassaforte è bloccata. Verifica la tua identità per continuare." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Cassaforte bloccata" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Il tuo account è bloccato" }, "or": { - "message": "or" + "message": "o" }, "unlock": { "message": "Sblocca" @@ -846,7 +852,7 @@ "message": "Accedi" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Accedi a Bitwarden" }, "restartRegistration": { "message": "Riprova la registrazione" @@ -882,10 +888,10 @@ "message": "La verifica in due passaggi rende il tuo account più sicuro richiedendoti di verificare il tuo login usando un altro dispositivo come una chiave di sicurezza, app di autenticazione, SMS, telefonata, o email. Può essere abilitata nella cassaforte web su bitwarden.com. Vuoi visitare il sito?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Rendi il tuo account più sicuro impostando l'autenticazione a due fattori nell'app web di Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Aprire web app?" }, "editedFolder": { "message": "Cartella salvata" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Mostra le identità nella sezione Scheda per riempirle automaticamente." }, + "clickToAutofillOnVault": { + "message": "Clicca gli oggetti da riempire dalla sezione Cassaforte" + }, "clearClipboard": { "message": "Cancella appunti", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ATTENZIONE", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Attenzione", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Conferma esportazione della cassaforte" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Sposta in organizzazione" }, - "share": { - "message": "Condividi" - }, "movedItemToOrg": { "message": "$ITEMNAME$ spostato in $ORGNAME$", "placeholders": { @@ -1196,7 +1206,7 @@ "message": "File" }, "fileToShare": { - "message": "File to share" + "message": "File da condividere" }, "selectFile": { "message": "Seleziona un file" @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Inserisci il codice di verifica a 6 cifre dalla tua app di autenticazione." }, + "authenticationTimeout": { + "message": "Timeout autenticazione" + }, + "authenticationSessionTimedOut": { + "message": "La sessione di autenticazione è scaduta. Accedi di nuovo." + }, "enterVerificationCodeEmail": { "message": "Inserisci il codice di verifica a 6 cifre inviato a $EMAIL$.", "placeholders": { @@ -1424,7 +1440,7 @@ "message": "URL del server" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL server autogestito", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1456,10 +1472,10 @@ "message": "Mostra suggerimenti di riempimento automatico nei campi del modulo" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Mostra identità come consigli" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Mostra carte come consigli" }, "showInlineMenuOnIconSelectionLabel": { "message": "Mostra suggerimenti quando l'icona è selezionata" @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Se sono rilevati campi di login, riempili automaticamente quando la pagina si carica." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Attenzione:$CLOSETAG$ Siti Web compromessi o non attendibili possono sfruttare il riempimento automatico al caricamento della pagina.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Siti compromessi potrebbero sfruttare il riempimento automatico al caricamento della pagina." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identità" }, + "typeSshKey": { + "message": "Chiave SSH" + }, "newItemHeader": { "message": "Nuovo $TYPE$", "placeholders": { @@ -1795,13 +1801,13 @@ "message": "Cronologia delle password" }, "generatorHistory": { - "message": "Generator history" + "message": "Cronologia generatore" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Cancella cronologia generatore" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Se continui, tutte le voci verranno eliminate definitivamente dalla cronologia del generatore. Vuoi continuare?" }, "back": { "message": "Indietro" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Note sicure" }, + "sshKeys": { + "message": "Chiavi SSH" + }, "clear": { "message": "Cancella", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "Cancella cronologia" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Niente da mostrare" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Non hai generato niente di recente" }, "remove": { "message": "Rimuovi" @@ -1984,16 +1993,16 @@ "message": "Sblocca con PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "Imposta PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "Imposta PIN" }, "setYourPinCode": { "message": "Imposta il tuo codice PIN per sbloccare Bitwarden. Le tue impostazioni PIN saranno resettate se esci completamente dall'app." }, "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": "Il tuo PIN sarà usato per sbloccare Bitwarden invece della password principale. Il PIN sarà ripristinato se ti disconnetterai completamente da Bitwarden." }, "pinRequired": { "message": "Codice PIN obbligatorio." @@ -2008,7 +2017,7 @@ "message": "Sblocca con i dati biometrici" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Sblocca con password principale" }, "awaitDesktop": { "message": "In attesa di conferma dal desktop" @@ -2020,7 +2029,7 @@ "message": "Blocca con la password principale al riavvio del browser" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Richiedi password principale al riavvio del browser" }, "selectOneCollection": { "message": "Devi selezionare almeno una raccolta." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clona" }, - "passwordGeneratorPolicyInEffect": { - "message": "Una o più politiche dell'organizzazione stanno influenzando le impostazioni del tuo generatore." - }, "passwordGenerator": { "message": "Generatore di password" }, @@ -2061,7 +2067,7 @@ "message": "Azione timeout cassaforte" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Azione al timeout" }, "lock": { "message": "Blocca", @@ -2318,6 +2324,9 @@ "message": "Domini", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domini bloccati" + }, "excludedDomains": { "message": "Domini esclusi" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non chiederà di salvare le credenziali di accesso per questi domini per tutti gli account sul dispositivo. Ricarica la pagina affinché le modifiche abbiano effetto." }, + "blockedDomainsDesc": { + "message": "Per questi siti, l'auto-completamento e funzionalità simili non saranno disponibili. Ricarica la pagina per applicare le modifiche." + }, + "autofillBlockedNotice": { + "message": "L'auto-completamento è bloccato per questo sito. Modifica questa scelta nelle impostazioni." + }, + "autofillBlockedTooltip": { + "message": "L'auto-completamento è bloccato per questo sito. Verifica nelle impostazioni." + }, "websiteItemLabel": { "message": "Sito $number$ (URI)", "placeholders": { @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Modifiche ai domini bloccati salvate" + }, "excludedDomainsSavedSuccess": { "message": "Modifiche del dominio escluso salvate" }, "limitSendViews": { - "message": "Limit views" + "message": "Limita visualizzazioni" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Nessuno potrà vedere questo Send al raggiungimento del limite.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ visualizzazioni rimaste", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2370,22 +2391,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "searchSends": { - "message": "Cerca Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Aggiungi Send", + "message": "Dettagli Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "Testo" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Testo da condividere" }, "sendTypeFile": { "message": "File" @@ -2395,18 +2408,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" - }, - "maxAccessCountReached": { - "message": "Numero massimo di accessi raggiunto", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + "message": "Nascondi testo come default" }, "expired": { "message": "Scaduto" }, - "pendingDeletion": { - "message": "In attesa di eliminazione" - }, "passwordProtected": { "message": "Protetto da password" }, @@ -2449,42 +2455,23 @@ "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": "Sicuro di voler eliminare definitivamente questo Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { "message": "Modifica Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Che tipo di Send è questo?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nome intuitivo per descrivere questo Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Il file da inviare." - }, "deletionDate": { "message": "Data di eliminazione" }, - "deletionDateDesc": { - "message": "Il Send sarà eliminato definitivamente alla data e ora specificate.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "Il Send sarà cancellato definitivamente in questa data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "Data di scadenza" }, - "expirationDateDesc": { - "message": "Se impostato, l'accesso a questo Send scadrà alla data e ora specificate.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 giorno" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalizzato" }, - "maximumAccessCount": { - "message": "Numero massimo di accessi" - }, - "maximumAccessCountDesc": { - "message": "Se impostata, gli utenti non potranno più accedere a questo Send una volta raggiunto il numero massimo di accessi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Richiedi una password agli utenti per accedere a questo Send (facoltativo).", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Note private sul Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Disattiva il Send per renderlo inaccessibile.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copia il link al Send negli appunti dopo averlo salvato.", + "message": "Richiedi ai destinatari una password opzionale per aprire questo Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTextDesc": { - "message": "Il testo che vuoi inviare." - }, - "sendHideText": { - "message": "Nascondi il testo di questo Send in modo predefinito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Numero di accessi correnti" - }, "createSend": { "message": "Nuovo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2561,11 +2515,11 @@ "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": "Il Send sarà disponibile a chiunque con il link per la prossima ora.", "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": "Il Send sarà disponibile a chiunque con il link per le prossime $HOURS$ ore.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2575,11 +2529,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "Il Send sarà disponibile a chiunque con il link per il prossimo giorno.", "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": "Il Send sarà disponibile a chiunque con il link per i prossimi $DAYS$ giorni.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2597,11 +2551,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": "Scollegare estensione?", "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": "Per creare un file Send, devi scollegare l'estensione in una nuova finestra.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2614,23 +2568,11 @@ "message": "Per scegliere un file usando Safari, apri una nuova finestra cliccando questo banner." }, "popOut": { - "message": "Pop out" + "message": "Scollega" }, "sendFileCalloutHeader": { "message": "Prima di iniziare" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Per usare un selettore di date stile calendario", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "clicca qui", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "per aprire la finestra.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "La data di scadenza fornita non è valida." }, @@ -2646,14 +2588,8 @@ "dateParsingError": { "message": "Si è verificato un errore durante il salvataggio delle date di eliminazione e scadenza." }, - "hideEmail": { - "message": "Nascondi il mio indirizzo email dai destinatari." - }, "hideYourEmail": { - "message": "Hide your email address from viewers." - }, - "sendOptionsPolicyInEffect": { - "message": "Una o più politiche dell'organizzazione stanno influenzando le tue opzioni di Send." + "message": "Nascondi il tuo indirizzo email ai visualizzatori." }, "passwordPrompt": { "message": "Richiedi di inserire la password principale di nuovo per visualizzare questo elemento" @@ -2710,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "di $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2729,7 +2665,7 @@ "message": "Minuti" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "I requisiti di politica aziendale sono stati applicati alle opzioni di timeout" }, "vaultTimeoutPolicyInEffect": { "message": "Le politiche della tua organizzazione hanno impostato il timeout massimo consentito della tua cassaforte su $HOURS$ ore e $MINUTES$ minuti.", @@ -2745,7 +2681,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Al massimo $HOURS$ ora/e e $MINUTES$ minuto/i.", "placeholders": { "hours": { "content": "$1", @@ -2758,7 +2694,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Il timeout supera la restrizione impostata dalla tua organizzazione: massimo $HOURS$ ora/e e $MINUTES$ minuto/i", "placeholders": { "hours": { "content": "$1", @@ -2868,17 +2804,28 @@ "error": { "message": "Errore" }, - "regenerateUsername": { - "message": "Rigenera nome utente" + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Genera nome utente" }, "generateEmail": { - "message": "Generate email" + "message": "Genera email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tipo di nome utente" + "passwordLengthRecommendationHint": { + "message": " Usa $RECOMMENDED$ caratteri o più per generare una password forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Usa $RECOMMENDED$ parole o più per generare una passphrase 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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Indirizzo email alternativo", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nome sito web" }, - "whatWouldYouLikeToGenerate": { - "message": "Cosa vuoi generare?" - }, - "passwordType": { - "message": "Tipo di password" - }, "service": { "message": "Servizio" }, @@ -2932,11 +2890,11 @@ "message": "Genera un alias email con un servizio di inoltro esterno." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Dominio email", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Scegli un dominio supportato dal servizio selezionato", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Invia notifica di nuovo" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Visualizza tutte le opzioni di accesso" + }, + "viewAllLoginOptionsV1": { "message": "Visualizza tutte le opzioni di accesso" }, "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Una notifica è stata inviata al tuo dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Assicurati che il tuo account sia sbloccato e che la frase dell'impronta digitale corrisponda nell'altro dispositivo" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Sarai notificato una volta che la richiesta sarà approvata" + }, + "needAnotherOptionV1": { + "message": "Bisogno di un'altra opzione?" + }, "loginInitiated": { "message": "Accesso avviato" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Si apre in una nuova finestra" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Ricorda questo dispositivo per rendere immediati i futuri accessi" + }, "deviceApprovalRequired": { "message": "Approvazione del dispositivo obbligatoria. Seleziona un'opzione di approvazione:" }, + "deviceApprovalRequiredV2": { + "message": "Approvazione dispositivo richiesta" + }, + "selectAnApprovalOptionBelow": { + "message": "Seleziona un'opzione di approvazione sotto" + }, "rememberThisDevice": { "message": "Ricorda questo dispositivo" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Email utente mancante" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Email utente attiva non trovata. Logout in corso." + }, "deviceTrusted": { "message": "Dispositivo fidato" }, @@ -3521,6 +3506,14 @@ "message": "Sblocca il tuo account, apri in una nuova finestra", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Codice di Verifica One-Time a tempo", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Tempo rimasto prima che l'attuale TOTP scada", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Riempi le credenziali per", "description": "Screen reader text for when overlay item is in focused" @@ -3747,7 +3740,10 @@ "message": "Passkey" }, "accessing": { - "message": "Accessing" + "message": "Accedendo a" + }, + "loggedInExclamation": { + "message": "Accesso effettuato!" }, "passkeyNotCopied": { "message": "La passkey non sarà copiata" @@ -3774,7 +3770,7 @@ "message": "Nessun login corrispondente per questo sito" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Cerca o salva la passkey come nuovo login" }, "confirm": { "message": "Conferma" @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtri" }, + "filterVault": { + "message": "Filtra cassaforte" + }, + "filterApplied": { + "message": "Un filtro applicato" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtri applicati", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Dati personali" }, @@ -4346,7 +4357,7 @@ "message": "Abilita animazioni" }, "showAnimations": { - "message": "Show animations" + "message": "Mostra animazioni" }, "addAccount": { "message": "Aggiungi account" @@ -4564,13 +4575,13 @@ "message": "Posizione elemento" }, "fileSend": { - "message": "File Send" + "message": "Send di File" }, "fileSends": { "message": "Send File" }, "textSend": { - "message": "Text Send" + "message": "Send di Testo" }, "textSends": { "message": "Send Testo" @@ -4587,20 +4598,47 @@ "showNumberOfAutofillSuggestions": { "message": "Mostra il numero di suggerimenti di riempimento automatico sull'icona dell'estensione" }, + "showQuickCopyActions": { + "message": "Mostra azioni di copia rapida nella Cassaforte" + }, "systemDefault": { "message": "Predefinito del sistema" }, "enterprisePolicyRequirementsApplied": { "message": "I requisiti della policy aziendale sono stati applicati a questa impostazione" }, + "sshPrivateKey": { + "message": "Chiave privata" + }, + "sshPublicKey": { + "message": "Chiave pubblica" + }, + "sshFingerprint": { + "message": "Impronta digitale" + }, + "sshKeyAlgorithm": { + "message": "Tipo di chiave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA a 2048 bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA a 3072 bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA a 4096 bit" + }, "retry": { - "message": "Retry" + "message": "Riprova" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Il timeout personalizzato minimo è 1 minuto." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Sono disponibili ulteriori contenuti" }, "fileSavedToDevice": { "message": "File salvato sul dispositivo. Gestisci dai download del dispositivo." @@ -4632,23 +4670,50 @@ "noEditPermissions": { "message": "Non hai i permessi per modificare questo elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Lo sblocco biometrico non è disponibile perché l'app desktop Bitwarden è chiusa." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authenticating": { - "message": "Authenticating" + "message": "Autenticazione" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Riempi password generata", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Password rigenerata", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Salvare il login su Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Spazio", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { @@ -4660,132 +4725,183 @@ "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Punto esclamativo", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Chiocciola", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Cancelletto", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Simbolo del dollaro", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Segno di percentuale", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Accento circonflesso", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "E commerciale", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Asterisco", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Parentesi sinistra", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Parentesi destra", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Trattino basso", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Trattino", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Più", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Uguale", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Parentesi graffa aperta", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Parentesi graffa chiusa", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Parentesi quadra aperta", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Parentesi quadra chiusa", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Barra verticale", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Barra rovesciata", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Due punti", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Punto e virgola", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Doppi apici", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrofo", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Minore", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Maggiore", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Virgola", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Punto", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Punto interrogativo", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Slash", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Minuscolo" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Maiuscolo" }, "generatedPassword": { "message": "Password generata" + }, + "compactMode": { + "message": "Modalità compatta" + }, + "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" + }, + "wide": { + "message": "Larga" + }, + "extraWide": { + "message": "Molto larga" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index f0baa16f9f3..cc1f34e4985 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -20,16 +20,16 @@ "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": "強力なパスワードを設定する" @@ -84,7 +84,7 @@ "message": "組織に参加" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "$ORGANIZATIONNAME$ に参加", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +120,7 @@ "message": "パスワードをコピー" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "パスフレーズをコピー" }, "copyNote": { "message": "メモをコピー" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "免許証番号をコピー" }, + "copyPrivateKey": { + "message": "秘密鍵をコピー" + }, + "copyPublicKey": { + "message": "公開鍵をコピー" + }, + "copyFingerprint": { + "message": "フィンガープリントをコピー" + }, "copyCustomField": { "message": "$FIELD$ をコピー", "placeholders": { @@ -168,7 +177,7 @@ "message": "メモをコピー" }, "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "自動入力 ID" }, + "fillVerificationCode": { + "message": "認証コードを入力" + }, + "fillVerificationCodeAria": { + "message": "認証コードを入力", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "パスワードを生成 (コピー)" }, @@ -427,7 +443,7 @@ "message": "パスワードの自動生成" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "パスフレーズを生成" }, "regeneratePassword": { "message": "パスワードの再生成" @@ -438,9 +454,6 @@ "length": { "message": "長さ" }, - "passwordMinLength": { - "message": "パスワードの最低文字数" - }, "uppercase": { "message": "大文字(A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "記号の最小数" }, - "avoidAmbChar": { - "message": "あいまいな文字を省く", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "あいまいな文字を避ける", "description": "Label for the avoid ambiguous characters checkbox." @@ -591,7 +600,7 @@ "message": "ウェブサイトを開く" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "ウェブサイト $ITEMNAME$ を開く", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "拡張機能の評価" }, - "rateExtensionDesc": { - "message": "良いレビューで私たちを助けてください!" - }, "browserNotSupportClipboard": { "message": "お使いのブラウザはクリップボードへのコピーに対応していません。手動でコピーしてください" }, @@ -846,7 +852,7 @@ "message": "ログイン" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden にログイン" }, "restartRegistration": { "message": "登録を再度始める" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "自動入力を簡単にするために、タブページに ID アイテムを表示します" }, + "clickToAutofillOnVault": { + "message": "保管庫で、自動入力するアイテムをクリックしてください" + }, "clearClipboard": { "message": "クリップボードの消去", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "注意", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "保管庫のエクスポートの確認" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "組織に移動" }, - "share": { - "message": "共有" - }, "movedItemToOrg": { "message": "$ITEMNAME$ を $ORGNAME$ に移動しました", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "認証アプリに表示された6桁の認証コードを入力してください。" }, + "authenticationTimeout": { + "message": "認証のタイムアウト" + }, + "authenticationSessionTimedOut": { + "message": "認証セッションの有効期限が切れました。ログインプロセスを再開してください。" + }, "enterVerificationCodeEmail": { "message": "$EMAIL$に送信された6桁の認証コードを入力してください。", "placeholders": { @@ -1424,7 +1440,7 @@ "message": "サーバー URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自己ホスト型サーバー URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1456,10 +1472,10 @@ "message": "フォームフィールドに自動入力の候補を表示する" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "ID を候補として表示する" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "カードを候補として表示する" }, "showInlineMenuOnIconSelectionLabel": { "message": "アイコンが選択されているときに候補を表示する" @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "ページ読み込み時にログインフォームを検出したとき、ログイン情報を自動入力します。" }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$警告:$CLOSETAG$ 侵害された、または信頼できないウェブサイトは、ページ読み込み時の自動入力を悪用する可能性があります。", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "ウイルス感染したり信頼できないウェブサイトは、ページの読み込み時の自動入力を悪用できてしまいます。" }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "ID" }, + "typeSshKey": { + "message": "SSH 鍵" + }, "newItemHeader": { "message": "$TYPE$ を新規作成", "placeholders": { @@ -1795,13 +1801,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": "戻る" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "セキュアメモ" }, + "sshKeys": { + "message": "SSH 鍵" + }, "clear": { "message": "消去する", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "履歴を消去" }, "nothingToShow": { - "message": "Nothing to show" + "message": "表示するものがありません" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "最近生成したものはありません" }, "remove": { "message": "削除" @@ -2031,9 +2040,6 @@ "clone": { "message": "複製" }, - "passwordGeneratorPolicyInEffect": { - "message": "一つ以上の組織のポリシーがパスワード生成の設定に影響しています。" - }, "passwordGenerator": { "message": "パスワード生成ツール" }, @@ -2318,6 +2324,9 @@ "message": "ドメイン", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "除外するドメイン" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "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." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "ウェブサイト $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "除外ドメインの変更を保存しました" }, @@ -2373,14 +2394,6 @@ "message": "Send の詳細", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Send を検索", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send を追加", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "テキスト" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "デフォルトでテキストを隠す" }, - "maxAccessCountReached": { - "message": "最大アクセス数に達しました", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "有効期限切れ" }, - "pendingDeletion": { - "message": "削除の保留中" - }, "passwordProtected": { "message": "パスワード保護あり" }, @@ -2456,24 +2462,9 @@ "message": "Send を編集", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "この Send の種類は何ですか?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "この Send を説明するわかりやすい名前", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "送信するファイル" - }, "deletionDate": { "message": "削除日時" }, - "deletionDateDesc": { - "message": "Send は指定された日時に完全に削除されます。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send はこの日付で完全に削除されます。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "有効期限" }, - "expirationDateDesc": { - "message": "設定されている場合、この Send へのアクセスは指定された日時に失効します。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1日" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "カスタム" }, - "maximumAccessCount": { - "message": "最大アクセス数" - }, - "maximumAccessCountDesc": { - "message": "設定されている場合、最大アクセス数に達するとユーザーはこの Send にアクセスできなくなります。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "必要に応じて、ユーザーがこの Send にアクセスするためのパスワードを要求します。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "この Send に関するプライベートメモ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "誰もアクセスできないように、この Send を無効にする", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "保存時にこの Send のリンクをクリップボードにコピーする", + "message": "受信者がこの Send にアクセスするための任意のパスワードを追加します。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTextDesc": { - "message": "送信したいテキスト" - }, - "sendHideText": { - "message": "この Send のテキストをデフォルトで非表示にする", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "現在のアクセス数" - }, "createSend": { "message": "新しい Send を作成", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "はじめる前に" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "カレンダースタイルの日付ピッカーを使用するには", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "こちらをクリック", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "してください。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "入力された有効期限は正しくありません。" }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "削除と有効期限の保存中にエラーが発生しました。" }, - "hideEmail": { - "message": "メールアドレスを受信者に表示しない" - }, "hideYourEmail": { "message": "閲覧者にメールアドレスを見せないようにします。" }, - "sendOptionsPolicyInEffect": { - "message": "一つ以上の組織ポリシーが Send の設定に影響しています。" - }, "passwordPrompt": { "message": "マスターパスワードの再要求" }, @@ -2868,17 +2804,28 @@ "error": { "message": "エラー" }, - "regenerateUsername": { - "message": "ユーザー名を再生成" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "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": "メールアドレスを生成" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "ユーザー名の種類" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "プラス付きのメールアドレス", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "ウェブサイト名" }, - "whatWouldYouLikeToGenerate": { - "message": "何を生成しますか?" - }, - "passwordType": { - "message": "パスワードの種類" - }, "service": { "message": "サービス" }, @@ -2932,11 +2890,11 @@ "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": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "通知を再送信する" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "すべてのログインオプションを表示" + }, + "viewAllLoginOptionsV1": { "message": "すべてのログインオプションを表示" }, "notificationSentDevice": { "message": "デバイスに通知を送信しました。" }, + "aNotificationWasSentToYourDevice": { + "message": "お使いのデバイスに通知が送信されました" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末で一致していることを確認してください" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "リクエストが承認されると通知されます" + }, + "needAnotherOptionV1": { + "message": "別の選択肢が必要ですか?" + }, "loginInitiated": { "message": "ログイン開始" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "新しいウィンドウで開く" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "このデバイスを記憶して今後のログインをシームレスにする" + }, "deviceApprovalRequired": { "message": "デバイスの承認が必要です。以下から承認オプションを選択してください:" }, + "deviceApprovalRequiredV2": { + "message": "デバイスの承認が必要です" + }, + "selectAnApprovalOptionBelow": { + "message": "以下の承認オプションを選択してください" + }, "rememberThisDevice": { "message": "このデバイスを記憶する" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "ユーザーのメールアドレスがありません" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "アクティブなユーザーメールアドレスが見つかりません。ログアウトします。" + }, "deviceTrusted": { "message": "信頼されたデバイス" }, @@ -3521,6 +3506,14 @@ "message": "アカウントのロックを解除し、新しいウィンドウで開く", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "時間ベースのワンタイムパスワード認証コード", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "現在の TOTP 有効期限が切れるまでの残り時間", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "資格情報を入力:", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "アクセス中" }, + "loggedInExclamation": { + "message": "ログインしました!" + }, "passkeyNotCopied": { "message": "パスキーはコピーされません" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "フィルター" }, + "filterVault": { + "message": "保管庫をフィルター" + }, + "filterApplied": { + "message": "1 個のフィルタを適用しました" + }, + "filterAppliedPlural": { + "message": "$COUNT$ 個のフィルタを適用しました", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "個人情報" }, @@ -4564,13 +4575,13 @@ "message": "アイテムの場所" }, "fileSend": { - "message": "File Send" + "message": "ファイル Send" }, "fileSends": { "message": "ファイル Send" }, "textSend": { - "message": "Text Send" + "message": "テキスト Send" }, "textSends": { "message": "テキスト Send" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "拡張機能アイコンにログイン自動入力の候補の数を表示する" }, + "showQuickCopyActions": { + "message": "保管庫にクイックコピー操作を表示する" + }, "systemDefault": { "message": "システムのデフォルト" }, "enterprisePolicyRequirementsApplied": { "message": "エンタープライズポリシー要件がこの設定に適用されました" }, + "sshPrivateKey": { + "message": "秘密鍵" + }, + "sshPublicKey": { + "message": "公開鍵" + }, + "sshFingerprint": { + "message": "フィンガープリント" + }, + "sshKeyAlgorithm": { + "message": "鍵の種類" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "再試行" }, @@ -4632,160 +4670,238 @@ "noEditPermissions": { "message": "このアイテムを編集する権限がありません" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "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?", + "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": "コンパクトモード" + }, + "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": "拡張機能の幅" + }, + "wide": { + "message": "ワイド" + }, + "extraWide": { + "message": "エクストラワイド" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index f57b4a50e48..50fdc6613c5 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "სიგრძე" }, - "passwordMinLength": { - "message": "პაროლის მინიმალური სიგრძე" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "ბუფერის გასუფთავება", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "გაფრთხილება", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "გაზიარება" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "იდენტიფიკაცია" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "გაწმენდა", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "კლონი" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "დომენები", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "ტექსტი" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "ვადაგასულია" }, - "pendingDeletion": { - "message": "ელოდება წაშლას" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "ვადა" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 დღე" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "განსხვავებული" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "აქ დააწკაპუნეთ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "შეცდომა" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "სერვისი" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "წვდომა" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "ფილტრები" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "სისტემურად ნაგულისხმევი" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "თავიდან ცდა" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "ავთენტიკაცია" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index b8075804229..e34751eea7d 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index a479b47fa46..3f9e99e5637 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "ಪಾಸ್ವರ್ಡ್ ರಚಿಸಿ (ನಕಲಿಸಲಾಗಿದೆ)" }, @@ -438,9 +454,6 @@ "length": { "message": "ಉದ್ದ" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "ಕನಿಷ್ಠ ವಿಶೇಷ" }, - "avoidAmbChar": { - "message": "ಅಸ್ಪಷ್ಟ ಅಕ್ಷರಗಳನ್ನು ತಪ್ಪಿಸಿ", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "ವಿಸ್ತರಣೆಯನ್ನು ರೇಟ್ ಮಾಡಿ" }, - "rateExtensionDesc": { - "message": "ಉತ್ತಮ ವಿಮರ್ಶೆಯೊಂದಿಗೆ ನಮಗೆ ಸಹಾಯ ಮಾಡಲು ದಯವಿಟ್ಟು ಪರಿಗಣಿಸಿ!" - }, "browserNotSupportClipboard": { "message": "ನಿಮ್ಮ ವೆಬ್ ಬ್ರೌಸರ್ ಸುಲಭವಾದ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ ನಕಲು ಮಾಡುವುದನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ. ಬದಲಿಗೆ ಅದನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ನಕಲಿಸಿ." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ ತೆರವುಗೊಳಿಸಿ", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ಎಚ್ಚರಿಕೆ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "ವಾಲ್ಟ್ ರಫ್ತು ಖಚಿತಪಡಿಸಿ" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "ಸಂಸ್ಥೆಗೆ ಸರಿಸಿ" }, - "share": { - "message": "ಹಂಚಿಕೊಳ್ಳಿ" - }, "movedItemToOrg": { "message": "$ITEMNAME$ ಅನ್ನು $ORGNAME$ ಗೆ ಸರಿಸಲಾಗಿದೆ", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "ನಿಮ್ಮ ದೃಢೀಕರಣ ಅಪ್ಲಿಕೇಶನ್‌ನಿಂದ 6 ಅಂಕಿಯ ಪರಿಶೀಲನಾ ಕೋಡ್ ಅನ್ನು ನಮೂದಿಸಿ." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ಗೆ ಇಮೇಲ್ ಮಾಡಲಾದ 6 ಅಂಕಿಯ ಪರಿಶೀಲನಾ ಕೋಡ್ ಅನ್ನು ನಮೂದಿಸಿ.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "ಲಾಗಿನ್ ಫಾರ್ಮ್ ಪತ್ತೆಯಾದಲ್ಲಿ, ವೆಬ್ ಪುಟ ಲೋಡ್ ಆಗುವಾಗ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸ್ವಯಂ ಭರ್ತಿ ಮಾಡಿ." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "ಗುರುತಿಸುವಿಕೆ" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "ಸುರಕ್ಷಿತ ಟಿಪ್ಪಣಿಗಳು" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "ಕ್ಲಿಯರ್‌", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "ಕ್ಲೋನ್" }, - "passwordGeneratorPolicyInEffect": { - "message": "ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಸಂಸ್ಥೆ ನೀತಿಗಳು ನಿಮ್ಮ ಜನರೇಟರ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರುತ್ತವೆ" - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "ಹೊರತುಪಡಿಸಿದ ಡೊಮೇನ್ಗಳು" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "ಹುಡುಕಾಟ ಕಳುಹಿಸುತ್ತದೆ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "ಕಳುಹಿಸು ಸೇರಿಸಿ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "ಪಠ್ಯ" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "ಗರಿಷ್ಠ ಪ್ರವೇಶ ಎಣಿಕೆ ತಲುಪಿದೆ", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "ಅವಧಿ ಮೀರಿದೆ" }, - "pendingDeletion": { - "message": "ಅಳಿಸುವಿಕೆ ಬಾಕಿ ಉಳಿದಿದೆ" - }, "passwordProtected": { "message": "ಪಾಸ್ವರ್ಡ್ ರಕ್ಷಿತ" }, @@ -2456,24 +2462,9 @@ "message": "ಕಳುಹಿಸು ಸಂಪಾದಿಸಿ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "ಇದು ಯಾವ ರೀತಿಯ ಕಳುಹಿಸುತ್ತದೆ?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "ಇದನ್ನು ಕಳುಹಿಸಲು ವಿವರಿಸಲು ಸ್ನೇಹಪರ ಹೆಸರು.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "ನೀವು ಕಳುಹಿಸಲು ಬಯಸುವ ಫೈಲ್." - }, "deletionDate": { "message": "ಅಳಿಸುವ ದಿನಾಂಕ" }, - "deletionDateDesc": { - "message": "ಕಳುಹಿಸಿದ ದಿನಾಂಕ ಮತ್ತು ಸಮಯದ ಮೇಲೆ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸಲಾಗುತ್ತದೆ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "ಮುಕ್ತಾಯ ದಿನಾಂಕ" }, - "expirationDateDesc": { - "message": "ಹೊಂದಿಸಿದ್ದರೆ, ಈ ಕಳುಹಿಸುವಿಕೆಯ ಪ್ರವೇಶವು ನಿಗದಿತ ದಿನಾಂಕ ಮತ್ತು ಸಮಯದ ಮೇಲೆ ಮುಕ್ತಾಯಗೊಳ್ಳುತ್ತದೆ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 ದಿನ" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "ಕಸ್ಟಮ್" }, - "maximumAccessCount": { - "message": "ಗರಿಷ್ಠ ಪ್ರವೇಶ ಎಣಿಕೆ" - }, - "maximumAccessCountDesc": { - "message": "ಹೊಂದಿಸಿದ್ದರೆ, ಗರಿಷ್ಠ ಪ್ರವೇಶ ಎಣಿಕೆ ತಲುಪಿದ ನಂತರ ಬಳಕೆದಾರರಿಗೆ ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ಬಳಕೆದಾರರಿಗೆ ಪಾಸ್‌ವರ್ಡ್ ಐಚ್ ಗತ್ಯವಿದೆ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "ಈ ಕಳುಹಿಸುವ ಬಗ್ಗೆ ಖಾಸಗಿ ಟಿಪ್ಪಣಿಗಳು.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "ಇದನ್ನು ಕಳುಹಿಸುವುದನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಇದರಿಂದ ಯಾರೂ ಅದನ್ನು ಪ್ರವೇಶಿಸಲಾಗುವುದಿಲ್ಲ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "ಉಳಿಸಿದ ನಂತರ ಈ ಕಳುಹಿಸುವ ಲಿಂಕ್ ಅನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಿ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "ನೀವು ಕಳುಹಿಸಲು ಬಯಸುವ ಪಠ್ಯ." - }, - "sendHideText": { - "message": "ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಈ ಕಳುಹಿಸುವ ಪಠ್ಯವನ್ನು ಮರೆಮಾಡಿ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "ಪ್ರಸ್ತುತ ಪ್ರವೇಶ ಎಣಿಕೆ" - }, "createSend": { "message": "ಹೊಸ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ರಚಿಸಿ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "ನೀವು ಪ್ರಾರಂಭಿಸುವ ಮೊದಲು" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "ಕ್ಯಾಲೆಂಡರ್ ಶೈಲಿಯ ದಿನಾಂಕ ಆಯ್ದುಕೊಳ್ಳುವಿಕೆಯನ್ನು ಬಳಸಲು", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "ಕ್ಲಿಕ್ ಮಾಡಿ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "ನಿಮ್ಮ ವಿಂಡೋವನ್ನು ಪಾಪ್ಔಟ್ ಮಾಡಲು.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "ಒದಗಿಸಿದ ಮುಕ್ತಾಯ ದಿನಾಂಕವು ಮಾನ್ಯವಾಗಿಲ್ಲ." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "ನಿಮ್ಮ ಅಳಿಸುವಿಕೆ ಮತ್ತು ಮುಕ್ತಾಯ ದಿನಾಂಕಗಳನ್ನು ಉಳಿಸುವಲ್ಲಿ ದೋಷ ಕಂಡುಬಂದಿದೆ." }, - "hideEmail": { - "message": "ಸ್ವೀಕರಿಸುವವರಿಂದ ನನ್ನ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಮರೆಮಾಡಿ." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಸಂಸ್ಥೆಯ ನೀತಿಗಳು ನಿಮ್ಮ ಕಳುಹಿಸುವ ಆಯ್ಕೆಗಳ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರುತ್ತವೆ." - }, "passwordPrompt": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಮರು-ಪ್ರಾಂಪ್ಟ್" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index ed1f9ded490..4ac6d281b09 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -20,16 +20,16 @@ "message": "계정 만들기" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden을 처음 이용하시나요?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "패스키를 사용하여 로그인하기" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "통합인증(SSO) 사용하기" }, "welcomeBack": { - "message": "Welcome back" + "message": "돌아온 것을 환영합니다." }, "setAStrongPassword": { "message": "비밀번호 설정" @@ -81,10 +81,10 @@ "message": "마스터 비밀번호 힌트 (선택)" }, "joinOrganization": { - "message": "Join organization" + "message": "\"조직\"에 가입하기" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "$ORGANIZATIONNAME$에 참가하기", "placeholders": { "organizationName": { "content": "$1", @@ -93,7 +93,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "마지막으로, 마스터 비밀번호를 설정하여 조직에 참가하십시오" }, "tab": { "message": "탭" @@ -120,7 +120,7 @@ "message": "비밀번호 복사" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "암호 복사" }, "copyNote": { "message": "메모 복사" @@ -152,8 +152,17 @@ "copyLicenseNumber": { "message": "운전면허 번호 복사" }, + "copyPrivateKey": { + "message": "개인 키 복사" + }, + "copyPublicKey": { + "message": "공개 키 복사" + }, + "copyFingerprint": { + "message": "핑거프린트 복사" + }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "$FIELD$ 복사", "placeholders": { "field": { "content": "$1", @@ -162,13 +171,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "웹사이트 복사" }, "copyNotes": { - "message": "Copy notes" + "message": "노트 복사" }, "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "신원 자동 완성" }, + "fillVerificationCode": { + "message": "인증 코드를 입력하세요" + }, + "fillVerificationCodeAria": { + "message": "인증 코드를 입력하세요", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "비밀번호 생성 및 클립보드에 복사" }, @@ -223,16 +239,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": "계정 이메일 주소를 입력하세요. 그 주소로 비밀번호 힌트가 전송될 것 입니다." }, "passwordHint": { "message": "비밀번호 힌트" @@ -265,7 +281,7 @@ "message": "마스터 비밀번호 변경" }, "continueToWebApp": { - "message": "웹 앱에서 계속하시겠용?" + "message": "웹 앱에서 계속하시겠나요?" }, "continueToWebAppDesc": { "message": "웹 앱에서 Bitwarden 계정의 더 많은 기능을 탐색해보세요." @@ -280,7 +296,7 @@ "message": "브라우저 확장 스토어로 이동하시겠습니까?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "다른 사람들이 Bitwarden이 적합한지 알 수 있도록 도와주세요. 당신의 브라우저 확장 스토어로 방문하여 별점을 남겨주세요." }, "changeMasterPasswordOnWebConfirmation": { "message": "Bitwarden 웹 앱에서 마스터 비밀번호를 변경할 수 있습니다." @@ -306,37 +322,37 @@ "message": "정보" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Bitwarden에 대한 더 많은 정보" }, "continueToBitwardenDotCom": { "message": "bitwarden.com 으로 이동할까요?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "비지니스용 Bitwarden" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "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 인증 도구를 사용하면, 인증키를 저장하고, 2단계 인증을 위한 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": "Free Bitwarden Families" + "message": "무료 bitwarden 가족 플랜" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "무료 Bitwarden 가족 플랜을 이용하실 수 있습니다. 오늘 웹앱에서 이 혜택을 사용하세요." }, "version": { "message": "버전" @@ -357,22 +373,22 @@ "message": "폴더 편집" }, "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": "폴더 삭제" @@ -415,7 +431,7 @@ "message": "유일무이하고 강력한 비밀번호를 자동으로 생성합니다." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden 웹 앱" }, "importItems": { "message": "항목 가져오기" @@ -427,7 +443,7 @@ "message": "비밀번호 생성" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "암호 생성" }, "regeneratePassword": { "message": "비밀번호 재생성" @@ -438,9 +454,6 @@ "length": { "message": "길이" }, - "passwordMinLength": { - "message": "최소 비밀번호 길이" - }, "uppercase": { "message": "대문자 (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -458,11 +471,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "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": { @@ -470,7 +483,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": { @@ -478,7 +491,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": { @@ -486,7 +499,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" }, "specialCharactersLabel": { @@ -512,16 +525,12 @@ "minSpecial": { "message": "특수 문자 최소 개수" }, - "avoidAmbChar": { - "message": "모호한 문자 사용 안 함", - "description": "deprecated. Use avoidAmbiguous instead." - }, "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": { @@ -558,16 +567,16 @@ "message": "즐겨찾기 해제" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "항목이 즐겨찾기에 추가되었습니다." }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "항목이 즐겨찾기에서 삭제되었습니다." }, "notes": { "message": "메모" }, "privateNote": { - "message": "Private note" + "message": "개인 메모" }, "note": { "message": "메모" @@ -591,7 +600,7 @@ "message": "웹사이트 열기" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "$ITEMNAME$ 웹사이드 열기", "placeholders": { "itemname": { "content": "$1", @@ -624,7 +633,7 @@ "message": "세션 만료" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "보관함 시간초과" }, "otherOptions": { "message": "기타 옵션" @@ -632,9 +641,6 @@ "rateExtension": { "message": "확장 프로그램 평가" }, - "rateExtensionDesc": { - "message": "좋은 리뷰를 남겨 저희를 도와주세요!" - }, "browserNotSupportClipboard": { "message": "사용하고 있는 웹 브라우저가 쉬운 클립보드 복사를 지원하지 않습니다. 직접 복사하세요." }, @@ -645,13 +651,13 @@ "message": "보관함이 잠겨 있습니다. 마스터 비밀번호를 입력하여 계속하세요." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "당신의 보관함이 잠겼습니다." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "당신의 계정이 잠겼습니다." }, "or": { - "message": "or" + "message": "또는" }, "unlock": { "message": "잠금 해제" @@ -676,7 +682,7 @@ "message": "보관함 시간 제한" }, "vaultTimeout1": { - "message": "Timeout" + "message": "시간초과" }, "lockNow": { "message": "지금 잠그기" @@ -730,16 +736,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": "오류가 발생했습니다" @@ -773,10 +779,10 @@ "message": "계정 생성이 완료되었습니다! 이제 로그인하실 수 있습니다." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "계정 생성이 완료되었습니다!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "로그인이 이미 되어있습니다." }, "youSuccessfullyLoggedIn": { "message": "로그인에 성공했습니다." @@ -791,7 +797,7 @@ "message": "인증 코드는 반드시 입력해야 합니다." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "인증이 너무 오래 걸리거나 취소되었습니다. 다시 시도하여 주십시오." }, "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" @@ -819,16 +825,16 @@ "message": "현재 웹페이지에서 QR 코드 스캔하기" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "간편하게 2단계 인증을 만들기" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden은 2단계 인증 코드들을 저장하고, 채워넣을 수 있습니다. 키를 복사하여 이 필드에 붙여넣으세요." }, "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은 2단계 인증 코드들을 저장하고, 채워넣을 수 있습니다. 카메라 아이콘을 선택하고, 이 웹사이드의 인증 도구 QR코드를 스크린샷을 찍거나, 키를 복사하여 이 필드에 붙여넣으세요." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "인증 도구에 대해 더 알아보기" }, "copyTOTP": { "message": "인증서 키 (TOTP) 복사" @@ -837,7 +843,7 @@ "message": "로그아웃됨" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "계정이 로그아웃 되었습니다." }, "loginExpired": { "message": "로그인 세션이 만료되었습니다." @@ -846,19 +852,19 @@ "message": "로그인" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden에 로그인" }, "restartRegistration": { - "message": "Restart registration" + "message": "등록 재시작" }, "expiredLink": { "message": "만료된 링크" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "등록 재시작 혹은 다시 로그인을 해주시길 바랍니다" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "계정을 이미 가지고 계실수도 있습니다." }, "logOutConfirmation": { "message": "정말 로그아웃하시겠습니까?" @@ -882,10 +888,10 @@ "message": "2단계 인증은 보안 키, 인증 앱, SMS, 전화 통화 등의 다른 기기로 사용자의 로그인 시도를 검증하여 사용자의 계정을 더욱 안전하게 만듭니다. 2단계 인증은 bitwarden.com 웹 보관함에서 활성화할 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Bitwarden 웹 앱에 2단계 인증을 설정하여, 당신의 계정을 좀 더 안전하게 만드세요." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "웹 앱으로 진행하나요?" }, "editedFolder": { "message": "폴더 편집함" @@ -972,7 +978,7 @@ "message": "로그인을 추가할 건지 물어보기" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "보관함 옵션들을 저장하기" }, "addLoginNotificationDesc": { "message": "\"로그인 추가 알림\"을 사용하면 새 로그인을 사용할 때마다 보관함에 그 로그인을 추가할 것인지 물어봅니다." @@ -981,22 +987,25 @@ "message": "보관함에 항목이 없을 경우 추가하라는 메시지를 표시합니다. 모든 로그인된 계정에 적용됩니다." }, "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "message": "보관함 보기에서 카드 자동완성 제안를 표시" }, "showCardsCurrentTab": { "message": "탭 페이지에 카드 표시" }, "showCardsCurrentTabDesc": { - "message": "List card items on the Tab page for easy autofill." + "message": "간편한 자동완성을 위해 탭에 카드 항목들을 나열" }, "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "message": "보관함 보기에서 신원들의 자동완성 제안을 표시" }, "showIdentitiesCurrentTab": { - "message": "Show identities on Tab page" + "message": "탭 페이지에 신원들을 표시" }, "showIdentitiesCurrentTabDesc": { - "message": "List identity items on the Tab page for easy autofill." + "message": "간편한 자동완성을 위해 탭에 신원 항목들을 나열" + }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" }, "clearClipboard": { "message": "클립보드 비우기", @@ -1034,7 +1043,7 @@ "message": "예, 지금 변경하겠습니다." }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the autofill request." + "message": "Bitwarden 보관함을 잠금 해제 하여 자동완성 요청을 완료하세요." }, "notificationUnlock": { "message": "잠금 해제" @@ -1043,13 +1052,13 @@ "message": "추가 옵션" }, "enableContextMenuItem": { - "message": "Show context menu options" + "message": "문맥 매뉴 옵션 표시" }, "contextMenuItemDesc": { - "message": "Use a secondary click to access password generation and matching logins for the website." + "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": "기본 URI 일치 인식", @@ -1080,7 +1089,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "~(으)로부터 내보내기" }, "exportVault": { "message": "보관함 내보내기" @@ -1089,19 +1098,19 @@ "message": "파일 형식" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "이 파일 내보내기는 비밀번호로 보호될 것이며, 파일을 해독하기 위해서는 파일 비밀번호가 필요합니다." }, "filePassword": { "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": "내보내기 유형" @@ -1110,12 +1119,16 @@ "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": "경고", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "보관함 내보내기 확인" }, @@ -1135,14 +1148,11 @@ "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은 조직을 사용하여 보관함 항목들을 다른 사람과 공유할 수 있게 해줍니다. 자세한 내용은 bitwarden.com 사이트에서 확인해주세요" }, "moveToOrganization": { "message": "조직으로 이동하기" }, - "share": { - "message": "공유" - }, "movedItemToOrg": { "message": "$ITEMNAME$이(가) $ORGNAME$(으)로 이동됨", "placeholders": { @@ -1196,7 +1206,7 @@ "message": "파일" }, "fileToShare": { - "message": "File to share" + "message": "공유할 파일" }, "selectFile": { "message": "파일을 선택하세요." @@ -1208,7 +1218,7 @@ "message": "기능 사용할 수 없음" }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "암호화 키 마이그레이션이 필요합니다. 웹 볼트를 통해 로그인하여 암호화 키를 업데이트하세요." }, "premiumMembership": { "message": "프리미엄 멤버십" @@ -1232,10 +1242,10 @@ "message": "1GB의 암호화된 파일 저장소." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "비상 접근" }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "YubiKey나 Duo와 같은 독점적인 2단계 로그인 옵션" }, "ppremiumSignUpReports": { "message": "보관함을 안전하게 유지하기 위한 암호 위생, 계정 상태, 데이터 유출 보고서" @@ -1256,7 +1266,7 @@ "message": "bitwarden.com 웹 보관함에서 프리미엄 멤버십을 구입할 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Bitwarden 웹 앱의 계정 설정에서 프리미엄에 대한 결제를 할 수 있습니다." }, "premiumCurrentMember": { "message": "프리미엄 사용자입니다!" @@ -1265,7 +1275,7 @@ "message": "Bitwarden을 지원해 주셔서 감사합니다." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "프리미엄으로 업그래이드 하고 받기: " }, "premiumPrice": { "message": "이 모든 기능을 연 $PRICE$에 이용하실 수 있습니다!", @@ -1277,7 +1287,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "이 모든 기능을 연 $PRICE$에 이용하실 수 있습니다!", "placeholders": { "price": { "content": "$1", @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "인증 앱에서 6자리 인증 코드를 입력하세요." }, + "authenticationTimeout": { + "message": "인증 시간 초과" + }, + "authenticationSessionTimedOut": { + "message": "인증 세션 시간이 초과 되었습니다. 다시 로그인을 시작해주세요." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ 주소로 전송된 6자리 인증 코드를 입력하세요.", "placeholders": { @@ -1370,17 +1386,17 @@ "message": "인증 앱" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Bitwarden 인증같은 인증 앱을 통해 코드를 생성하여 입력해주세요", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "YubiKey OTP 보안 키" }, "yubiKeyDesc": { "message": "YubiKey를 사용하여 사용자의 계정에 접근합니다. YubiKey 4, 4 Nano, 4C 및 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": { @@ -1397,7 +1413,7 @@ "message": "이메일" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "이메일로 전송된 코드를 입력하세요." }, "selfHostedEnvironment": { "message": "자체 호스팅 환경" @@ -1406,13 +1422,13 @@ "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 URL을 지정하세요." }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 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": "기본 서버 URL이나 최소한 하나의 사용자 지정 환경을 추가해야 합니다." }, "customEnvironment": { "message": "사용자 지정 환경" @@ -1424,7 +1440,7 @@ "message": "서버 URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "자체 호스트 서버 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1450,28 +1466,28 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "자동 완성 제안" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "양식 필드에 자동 완성 제안 표시" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "신원를 제안으로 표시" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "카드를 제안으로 표시" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "아이콘을 선택하면 제안이 표시됩니다." }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "로그인한 모든 계정에 적용" }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "충돌을 방지하기 위해 브라우저의 기본 암호 관리 설정을 해제합니다." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "브라우저 설정 편집" }, "autofillOverlayVisibilityOff": { "message": "끄기", @@ -1486,7 +1502,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": "페이지 로드 시 자동 완성 사용" @@ -1494,27 +1510,14 @@ "enableAutoFillOnPageLoadDesc": { "message": "로그인 양식을 감지하면 웹 페이지 로드 시 자동 완성을 자동으로 수행합니다." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "취약하거나 신뢰할 수 없는 웹사이트 페이지 로드 시 자동 완성이 악용될 수 있습니다." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "위험에 대해 자세히 알아보기" }, "learnMoreAboutAutofill": { - "message": "Learn more about autofill" + "message": "자동 완정에 대해 자세히 할아보기" }, "defaultAutoFillOnPageLoad": { "message": "로그인 항목에 대한 기본 자동 완성 설정" @@ -1541,13 +1544,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": "새 무작위 비밀번호를 만들고 클립보드에 복사합니다." @@ -1580,7 +1583,7 @@ "message": "참 / 거짓" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "체크박스" }, "cfTypeLinked": { "message": "연결됨", @@ -1600,7 +1603,7 @@ "message": "웹사이트 아이콘 표시하기" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "로그인 정보 옆에 식별용 이미지를 표시합니다." }, "faviconDescAlt": { "message": "각 로그인 정보 옆에 인식할 수 있는 이미지를 표시합니다. 모든 로그인된 계정에 적용됩니다." @@ -1764,8 +1767,11 @@ "typeIdentity": { "message": "신원" }, + "typeSshKey": { + "message": "SSH 키" + }, "newItemHeader": { - "message": "New $TYPE$", + "message": "새 $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1774,7 +1780,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "$TYPE$ 수정", "placeholders": { "type": { "content": "$1", @@ -1783,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "$TYPE$ 보기", "placeholders": { "type": { "content": "$1", @@ -1795,13 +1801,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": "뒤로" @@ -1810,7 +1816,7 @@ "message": "컬렉션" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ 컬렉션", "placeholders": { "count": { "content": "$1", @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "보안 메모" }, + "sshKeys": { + "message": "SSH 키" + }, "clear": { "message": "삭제", "description": "To clear something out. example: To clear browser history." @@ -1863,7 +1872,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "기본 도메인 (추천)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1917,13 +1926,13 @@ "message": "비밀번호가 없습니다." }, "clearHistory": { - "message": "Clear history" + "message": "기록 지우기" }, "nothingToShow": { - "message": "Nothing to show" + "message": "항목 없음" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "최근에 생성한 것이 없습니다" }, "remove": { "message": "제거" @@ -1984,16 +1993,16 @@ "message": "PIN 코드를 사용하여 잠금 해제" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "PIN 설정" }, "setYourPinButton": { - "message": "Set PIN" + "message": "PIN 설정" }, "setYourPinCode": { "message": "Bitwarden 잠금해제에 사용될 PIN 코드를 설정합니다. 이 애플리케이션에서 완전히 로그아웃할 경우 PIN 설정이 초기화됩니다." }, "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": "PIN은 마스터 비밀번호 대신 Bitwarden 잠금해제에 사용됩니다. Bitwarden에서 완전히 로그아웃하면 PIN이 재설정됩니다." }, "pinRequired": { "message": "PIN 코드가 필요합니다." @@ -2008,7 +2017,7 @@ "message": "생체 인식을 사용하여 잠금 해제" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "마스터 비밀번호로 잠금 해제" }, "awaitDesktop": { "message": "데스크톱으로부터의 확인을 대기 중" @@ -2020,7 +2029,7 @@ "message": "브라우저 다시 시작 시 마스터 비밀번호로 잠금" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "브라우저 다시 시작 시 마스터 비밀번호가 필요합니다" }, "selectOneCollection": { "message": "반드시 하나 이상의 컬렉션을 선택해야 합니다." @@ -2031,37 +2040,34 @@ "clone": { "message": "복제" }, - "passwordGeneratorPolicyInEffect": { - "message": "하나 이상의 단체 정책이 생성기 규칙에 영항을 미치고 있습니다." - }, "passwordGenerator": { - "message": "Password generator" + "message": "비밀번호 생성기" }, "usernameGenerator": { - "message": "Username generator" + "message": "사용자 이름 생성기" }, "useThisPassword": { - "message": "Use this password" + "message": "이 비밀번호 사용" }, "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'" }, "vaultTimeoutAction": { "message": "보관함 시간 제한 초과시 동작" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "시간초과 시 행동" }, "lock": { "message": "잠금", @@ -2090,7 +2096,7 @@ "message": "복원된 항목" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "이미 계정이 있으신가요?" }, "vaultTimeoutLogOutConfirmation": { "message": "로그아웃하면 보관함에 대한 모든 접근이 제거되며 시간 제한을 초과하면 온라인 인증을 요구합니다. 정말로 이 설정을 사용하시겠습니까?" @@ -2102,7 +2108,7 @@ "message": "자동 완성 및 저장" }, "fillAndSave": { - "message": "Fill and save" + "message": "채우기 및 저장" }, "autoFillSuccessAndSavedUri": { "message": "항목을 자동 완성하고 URI를 저장함" @@ -2111,16 +2117,16 @@ "message": "항목을 자동 완성함" }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "경고: 이 페이지는 보안이 해제된 HTTP 페이지이며, 제출한 모든 정보는 다른 사람이 보고 변경할 수 있습니다. 이 로그인은 원래 보안(HTTPS) 페이지에 저장되었습니다." }, "insecurePageWarningFillPrompt": { - "message": "Do you still wish to fill this login?" + "message": "여전히 이 로그인을 채우시겠습니까?" }, "autofillIframeWarning": { - "message": "The form is hosted by a different domain than the URI of your saved login. Choose OK to autofill anyway, or Cancel to stop." + "message": "양식은 저장된 로그인의 URI가 아닌 다른 도메인에서 호스팅됩니다. 그래도 자동 완성을 사용하시려면 OK, 아니라면 취소 버튼을 선택해주세요." }, "autofillIframeWarningTip": { - "message": "To prevent this warning in the future, save this URI, $HOSTNAME$, to your Bitwarden login item for this site.", + "message": "향후 이 경고를 방지하려면 이 URI인 $HOSTNAME$(을)를 Bitwarden로그인 항목에 저장하세요.", "placeholders": { "hostname": { "content": "$1", @@ -2183,25 +2189,25 @@ "message": "새 마스터 비밀번호가 정책 요구 사항을 따르지 않습니다." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Email 받은 편지함을 통해 Bitwarden의 조언, 공지사항 및 연구 기회들을 얻어보세요" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "구독 취소" }, "atAnyTime": { - "message": "at any time." + "message": "언제든지" }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "계속하면 다음에 동의하게 됩니다" }, "and": { - "message": "and" + "message": "그리고" }, "acceptPolicies": { "message": "이 박스를 체크하면 다음에 동의하는 것으로 간주됩니다:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "서비스 약관 및 개인 정보 보호 정책을 확인하지 않았습니다." }, "termsOfService": { "message": "서비스 약관" @@ -2216,10 +2222,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": "데스크톱과의 동기화 인증" @@ -2258,10 +2264,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": "생체 인식이 활성화되지 않음" @@ -2276,22 +2282,22 @@ "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": "Biometrics failed" + "message": "생체 인식 실패" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "생체 인식을 완료할 수 없습니다. 마스터 비밀번호를 사용하거나 로그아웃하는 것을 고려하세요. 이 문제가 계속되면 Bitwarden 지원팀에 문의해 주세요." }, "nativeMessaginPermissionErrorTitle": { "message": "권한이 부여되지 않음" @@ -2312,12 +2318,15 @@ "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" + }, "excludedDomains": { "message": "제외된 도메인" }, @@ -2325,10 +2334,19 @@ "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." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "웹사이트 $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "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": { @@ -2366,26 +2387,18 @@ } }, "send": { - "message": "Send", + "message": "보내기", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "searchSends": { - "message": "Send 검색", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send 추가", + "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": "파일" @@ -2395,26 +2408,19 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" - }, - "maxAccessCountReached": { - "message": "최대 접근 횟수 도달", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + "message": "기본적으로 텍스트 숨기기" }, "expired": { "message": "만료됨" }, - "pendingDeletion": { - "message": "삭제 대기 중" - }, "passwordProtected": { "message": "비밀번호로 보호됨" }, "copyLink": { - "message": "Copy link" + "message": "링크 복사" }, "copySendLink": { - "message": "Send 링크 복사", + "message": " Send 링크 복사", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -2427,7 +2433,7 @@ "message": "비밀번호 제거함" }, "deletedSend": { - "message": "Send 삭제함", + "message": " Send 삭제함", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { @@ -2441,55 +2447,36 @@ "message": "비밀번호를 제거하시겠습니까?" }, "deleteSend": { - "message": "Send 삭제", + "message": " Send 삭제", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "정말 이 Send를 삭제하시겠습니까?", + "message": "정말 이 Send를 삭제하시겠습니까?", "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": "이 Send을 영구적으로 삭제하시겠습니까?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "Send 편집", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTypeHeader": { - "message": "어떤 유형의 Send인가요?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "이 Send의 이름", + "message": " Send 편집", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendFileDesc": { - "message": "전송하려는 파일" - }, "deletionDate": { "message": "삭제 날짜" }, - "deletionDateDesc": { - "message": "이 Send가 정해진 일시에 영구적으로 삭제됩니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "이 Send가 이 날짜에 영구적으로 삭제됩니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "만료 날짜" }, - "expirationDateDesc": { - "message": "설정할 경우, 이 Send에 대한 접근 권한이 정해진 일시에 만료됩니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1일" }, "days": { - "message": "$DAYS$일", + "message": "$DAYS$ 일", "placeholders": { "days": { "content": "$1", @@ -2500,43 +2487,10 @@ "custom": { "message": "사용자 지정" }, - "maximumAccessCount": { - "message": "최대 접근 횟수" - }, - "maximumAccessCountDesc": { - "message": "설정할 경우, 최대 접근 횟수에 도달할 때 이 Send에 접근할 수 없게 됩니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "이 Send에 접근하기 위해 암호를 입력하도록 선택적으로 요구합니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "이 Send에 대한 비공개 메모", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "이 Send를 비활성화하여 아무도 접근할 수 없게 합니다.", + "message": "수신자가 이 Send에 액세스할 수 있도록 비밀번호 옵션를 추가합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendShareDesc": { - "message": "저장할 때 이 Send의 링크를 클립보드에 복사합니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "전송하려는 텍스트" - }, - "sendHideText": { - "message": "이 Send의 텍스트를 기본적으로 숨김", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "현재 접근 횟수" - }, "createSend": { "message": "새 Send 생성", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2557,15 +2511,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": "Send가 성공적으로 생성되었습니다!", "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": "이 Send는 링크가 있는 누구나 향후 1시간 동안 이용할 수 있습니다.", "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": { @@ -2575,11 +2529,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "이 Send은 향후 1일 동안 링크가 있는 누구나 이용할 수 있습니다.", "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": "이 Send은 향후 $DAYS$일 동안 링크가 있는 누구나 이용할 수 있습니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2589,19 +2543,19 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send 링크 복사됨", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send 수정함", + "message": "Send 수정됨", "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": "파일 Send를 만들려면, 새 창으로 확장자를 열어야 합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2614,23 +2568,11 @@ "message": "Safari에서 파일을 선택할 경우, 이 배너를 클릭하여 확장 프로그램을 새 창에서 여세요." }, "popOut": { - "message": "Pop out" + "message": "새 창에서 열기" }, "sendFileCalloutHeader": { "message": "시작하기 전에" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "달력을 보고 날짜를 선택하려면", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "여기를 클릭하여", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "확장 프로그램을 새 창에서 여세요.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "제공된 만료 날짜가 유효하지 않습니다." }, @@ -2646,14 +2588,8 @@ "dateParsingError": { "message": "삭제 날짜와 만료 날짜를 저장하는 도중 오류가 발생했습니다." }, - "hideEmail": { - "message": "받는 사람으로부터 나의 이메일 주소 숨기기" - }, "hideYourEmail": { - "message": "Hide your email address from viewers." - }, - "sendOptionsPolicyInEffect": { - "message": "하나 이상의 단체 정책이 Send 설정에 영향을 미치고 있습니다." + "message": "사람들로부터 이메일 주소를 숨기세요." }, "passwordPrompt": { "message": "마스터 비밀번호 재확인" @@ -2668,7 +2604,7 @@ "message": "이메일 인증 필요함" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "이메일 인증됨" }, "emailVerificationRequiredDesc": { "message": "이 기능을 사용하려면 이메일 인증이 필요합니다. 웹 보관함에서 이메일을 인증할 수 있습니다." @@ -2683,10 +2619,10 @@ "message": "최근에 조직 관리자가 마스터 비밀번호를 변경했습니다. 보관함에 액세스하려면 지금 업데이트해야 합니다. 계속하면 현재 세션에서 로그아웃되며 다시 로그인해야 합니다. 다른 장치의 활성 세션은 최대 1시간 동안 계속 활성 상태로 유지될 수 있습니다." }, "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": "마스터 비밀번호가 조직 정책 중 하나 이상을 충족하지 못합니다. 보관함에 액세스하려면, 지금 마스터 비밀번호를 업데이트해야 합니다. 계속 진행하면 현재 세션에서 로그아웃되므로, 다시 로그인해야 합니다. 다른 장치에서 활성 세션은 최대 1시간 동안 계속 활성 상태로 유지될 수 있습니다." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "조직에서 신뢰할 수 있는 장치 암호화를 비활성화했습니다. 보관함에 접근하려면 마스터 비밀번호를 설정하세요." }, "resetPasswordPolicyAutoEnroll": { "message": "자동 등록" @@ -2702,15 +2638,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", @@ -2729,7 +2665,7 @@ "message": "분" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "타임아웃 옵션에 기업의 정책 요구 사항이 적용되었습니다" }, "vaultTimeoutPolicyInEffect": { "message": "조직 정책이 보관함 제한 시간에 영향을 미치고 있습니다. 최대 허용 보관함 제한 시간은 $HOURS$시간 $MINUTES$분입니다", @@ -2745,7 +2681,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "최대 $HOURS$시간 $MINUTES$분", "placeholders": { "hours": { "content": "$1", @@ -2758,7 +2694,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", @@ -2771,7 +2707,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", @@ -2788,7 +2724,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "조직 정책에 따라 보관함 타임아웃 작업이 $ACTION$으로 설정되었습니다.", "placeholders": { "action": { "content": "$1", @@ -2797,7 +2733,7 @@ } }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "보관함 시간 초과가 조직에서 설정한 제한을 초과합니다." }, "vaultExportDisabled": { "message": "보관함 내보내기 비활성화됨" @@ -2845,7 +2781,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", @@ -2854,10 +2790,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", @@ -2868,17 +2804,28 @@ "error": { "message": "오류" }, - "regenerateUsername": { - "message": "아이디 재생성" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "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": "이메일 생성" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "값은 $MIN$과 $MAX$ 사이여야 합니다", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,21 +2838,38 @@ } } }, - "usernameType": { - "message": "아이디 유형" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "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": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "이메일 제공업체의 하위 주소 지정 기능을 사용하세요." }, "catchallEmail": { - "message": "Catch-all email" + "message": "Catch-all 이메일 (도메인 상의 어떤 주소로도 전송된 이메일을 받을 수 있는 주소)" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "catch-all이 설정된 내 도메인의 메일함을 사용하세요." }, "random": { "message": "무작위" @@ -2916,12 +2880,6 @@ "websiteName": { "message": "웹사이트 이름" }, - "whatWouldYouLikeToGenerate": { - "message": "무엇을 생성하실건가요?" - }, - "passwordType": { - "message": "비밀번호 유형" - }, "service": { "message": "서비스" }, @@ -2929,18 +2887,18 @@ "message": "포워딩된 이메일 별칭" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "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": { @@ -2954,11 +2912,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": { @@ -2968,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "잘못된 $SERVICENAME$ API 토큰\n", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2978,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "잘못된 $SERVICENAME$ API 토큰: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2992,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "$SERVICENAME$ 마스크된 이메일 계정 ID를 얻을 수 없습니다.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3002,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "잘못된 $SERVICENAME$ 도메인.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3012,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "잘못된 $SERVICENAME$ URL", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3022,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "알 수 없는 $SERVICENAME$ 오류가 발생했습니다.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3032,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "알 수 없는 포워더: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3058,13 +3016,13 @@ "message": "프리미엄 구독이 필요합니다" }, "organizationIsDisabled": { - "message": "Organization suspended." + "message": "조직이 중지됨" }, "disabledOrganizationFilterError": { - "message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "중단된 조직의 항목에 액세스할 수 없습니다. 조직 소유자에게 도움을 요청하세요." }, "loggingInTo": { - "message": "Logging in to $DOMAIN$", + "message": "$DOMAIN$(으)로 로그인", "placeholders": { "domain": { "content": "$1", @@ -3073,13 +3031,13 @@ } }, "settingsEdited": { - "message": "Settings have been edited" + "message": "설정이 편집되었습니다" }, "environmentEditedClick": { - "message": "Click here" + "message": "여기를 클릭하세요." }, "environmentEditedReset": { - "message": "to reset to pre-configured settings" + "message": "사전 구성된 설정으로 재설정하려면" }, "serverVersion": { "message": "서버 버전" @@ -3091,7 +3049,7 @@ "message": "제 3자" }, "thirdPartyServerMessage": { - "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", + "message": "제 3자 서버 구현에 연결되었습니다. $SERVERNAME$. 공식 서버를 사용하여 버그를 확인하거나 타사 서버에 보고해 주세요.", "placeholders": { "servername": { "content": "$1", @@ -3100,7 +3058,7 @@ } }, "lastSeenOn": { - "message": "last seen on: $DATE$", + "message": "확인된 날짜: $DATE$", "placeholders": { "date": { "content": "$1", @@ -3109,10 +3067,10 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "마스터 비밀번호로 로그인" }, "loggingInAs": { - "message": "Logging in as" + "message": "다음으로 로그인 중" }, "notYou": { "message": "본인이 아닌가요?" @@ -3124,52 +3082,67 @@ "message": "이메일 기억하기" }, "loginWithDevice": { - "message": "Log in with device" + "message": "기기로 로그인" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "기기로 로그인하려면 Bitwarden 모바일 앱 설정에서 설정해야 합니다. 다른 방식이 필요하신가요?" }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "지문 구절" }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device." + "message": "반드시 보관함이 잠금 해제되었고, 지문 구절이 다른 기기에서 일치하는지 확인해주세요." }, "resendNotification": { - "message": "Resend notification" + "message": "알림 다시 보내기" + }, + "viewAllLogInOptions": { + "message": "모든 로그인 방식 보기" }, - "viewAllLoginOptions": { - "message": "View all log in options" + "viewAllLoginOptionsV1": { + "message": "모든 로그인 옵션 보기" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "기기에 알림이 전송되었습니다." + }, + "aNotificationWasSentToYourDevice": { + "message": "기기에 알림이 전송되었습니다." + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "반드시 계정이 잠금 해제되었고, 지문 구절이 다른 기기에서 일치하는지 확인해주세요." + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "요청이 승인되면 알림을 받게 됩니다" + }, + "needAnotherOptionV1": { + "message": "다른 옵션이 필요하신가요?" }, "loginInitiated": { - "message": "Login initiated" + "message": "로그인 시작" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "노출된 마스터 비밀번호" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "데이터 유출이 된 비밀번호임이 발견되었습니다. 계정을 보호하려면 고유한 비밀번호를 사용하세요. 노출된 비밀번호를 사용하시겠습니까?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "취약하고 노출된 마스터 비밀번호" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "데이터 유출이 된 약한 비밀번호임이 발견되었습니다. 계정을 보호하려면 강력하고 고유한 비밀번호를 사용하세요. 이 비밀번호를 사용하시겠습니까?" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "이 비밀번호에 대한 알려진 데이터 유출 확인\n" }, "important": { - "message": "Important:" + "message": "중요:" }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "마스터 비밀번호를 잊어버리면 복구할 수 없습니다!\n" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "최소 $LENGTH$ 문자", "placeholders": { "length": { "content": "$1", @@ -3178,13 +3151,13 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Your organization policies have turned on autofill on page load." + "message": "조직 정책에 따라, 페이지 로드 시 자동 완성 기능을 켰습니다." }, "howToAutofill": { - "message": "How to autofill" + "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", @@ -3193,31 +3166,31 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "이 화면에서 항목을 선택하거나 설정의 다른 옵션을 탐색하세요." }, "gotIt": { - "message": "Got it" + "message": "이해했습니다" }, "autofillSettings": { "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", @@ -3226,7 +3199,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Default autofill shortcut: $COMMAND$.", + "message": "기본 자동 완성 바로 가기: $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -3235,56 +3208,65 @@ } }, "opensInANewWindow": { - "message": "Opens in a new window" + "message": "새 창에서 열립니다" + }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "향후 로그인을 원활하게 하기 위해 이 기기 기억하기" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "기기 승인이 필요합니다. 아래에서 승인 옵션을 선택하세요:" + }, + "deviceApprovalRequiredV2": { + "message": "기기 승인이 필요합니다." + }, + "selectAnApprovalOptionBelow": { + "message": "아래에서 승인 옵션을 선택하세요" }, "rememberThisDevice": { "message": "이 기기 기억하기" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "공용 기기를 사용하는 경우 체크 해제" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "다른 장치에서 승인" }, "requestAdminApproval": { - "message": "관리자 승인 필요" + "message": "관리자 인증 필요" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "마스터 비밀번호로 승인" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "조직의 SSO 식별자가 필요합니다" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "계정 만들기" }, "checkYourEmail": { - "message": "Check your email" + "message": "이메일을 확인해주세요" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "이메일로 전송한 링크를 통해" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "계정을 계속 생성하세요." }, "noEmail": { - "message": "No email?" + "message": "이메일이 전송되지 않았나요?" }, "goBack": { - "message": "Go back" + "message": "뒤로 돌아가서" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "이메일 주소를 수정하기" }, "eu": { "message": "EU", "description": "European Union" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "접근이 거부되었습니다. 이 페이지를 볼 권한이 없습니다." }, "general": { "message": "일반" @@ -3299,42 +3281,45 @@ "message": "관리자 승인 필요" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "요청이 관리자에게 전송되었습니다." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "승인되면 알림을 받게 됩니다." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "로그인에 문제가 있나요?" }, "loginApproved": { - "message": "Login approved" + "message": "로그인 승인됨" }, "userEmailMissing": { - "message": "User email missing" + "message": "사용자 이메일 누락" + }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "활성화된 사용자의 이메일을 찾을 수 없습니다. 로그아웃합니다." }, "deviceTrusted": { - "message": "Device trusted" + "message": "신뢰할 수 있는 장치" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "활성화된 Send없음", "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.", + "message": "Send를 사용하여 암호화된 정보를 어느 사람과도 안전하게 공유합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "입력이 필요합니다." }, "required": { - "message": "required" + "message": "필수" }, "search": { "message": "검색" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "입력은 최소한 $COUNT$자 이상이어야 합니다.", "placeholders": { "count": { "content": "$1", @@ -3343,7 +3328,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "입력 길이는 $COUNT$자를 초과해서는 안 됩니다.", "placeholders": { "count": { "content": "$1", @@ -3352,7 +3337,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "다음 문자는 허용되지 않습니다: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3361,7 +3346,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "입력 값은 최소 $MIN$자 이상이어야 합니다.", "placeholders": { "min": { "content": "$1", @@ -3370,7 +3355,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "입력 값은 $MAX$ 자를 초과해서는 안 됩니다.", "placeholders": { "max": { "content": "$1", @@ -3382,14 +3367,14 @@ "message": "하나 이상의 이메일이 유효하지 않습니다." }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "입력에는 공백만 포함해서는 안 됩니다.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "입력이 이메일 주소가 아닙니다" }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "위의 $COUNT$ 필드에 주의가 필요합니다", "placeholders": { "count": { "content": "$1", @@ -3398,10 +3383,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1개의 필드가 주의가 필요합니다." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ 개의 필드가 주의가 필요합니다.", "placeholders": { "count": { "content": "$1", @@ -3410,22 +3395,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- 선택 --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "- 필터링할 유형 --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "옵션을 검색하는 중..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "항목을 찾을 수 없습니다" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "모두 지우기" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ $QUANTITY$개 더보기", "placeholders": { "quantity": { "content": "$1", @@ -3434,30 +3419,30 @@ } }, "submenu": { - "message": "Submenu" + "message": "하위 메뉴" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "토글이 붕괴됨", "description": "Toggling an expand/collapse state." }, "filelessImport": { - "message": "Import your data to Bitwarden?", + "message": "데이터를 Bitwarden으로 가져오시겠습니까?", "description": "Default notification title for triggering a fileless import." }, "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", + "message": "LastPass 데이터를 보호하고 Bitwarden으로 가져오시겠습니까?", "description": "LastPass specific notification title for triggering a fileless import." }, "lpCancelFilelessImport": { - "message": "Save as unencrypted file", + "message": "암호화되지 않은 파일로 저장", "description": "LastPass specific notification button text for cancelling a fileless import." }, "startFilelessImport": { - "message": "Import to Bitwarden", + "message": "Bitwarden으로 가져오기", "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importing...", + "message": "가져오는 중...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { @@ -3465,52 +3450,52 @@ "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "가져오는 중 오류가 발생했습니다. 자세한 내용은 콘솔을 확인하세요.", "description": "Notification message for when an import has failed." }, "importNetworkError": { - "message": "Network error encountered during import.", + "message": "가져오기 중에 네트워크 오류가 발생했습니다.", "description": "Notification message for when an import has failed due to a network error." }, "aliasDomain": { - "message": "Alias domain" + "message": "도메인 별칭" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be autofilled on page load. Autofill on page load turned off.", + "message": "마스터 비밀번호 재 요청이 있는 항목은 페이지 로드에서 자동으로 채울 수 없습니다. 페이지 로드의 자동 완성이 꺼졌습니다.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "페이지 로드 시 자동 완성이 기본 설정을 사용하도록 설정되었습니다.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "마스터 암호 재 요청을 해제하여 이 필드를 편집합니다", "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": { @@ -3518,19 +3503,27 @@ "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": "TOTP 인증 코드", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "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": { - "message": "No items to show", + "message": "표시할 항목 없음", "description": "Text to show in overlay if there are no matching items" }, "newItem": { @@ -3538,64 +3531,64 @@ "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Add new vault item", + "message": "새 보관함 항목 추가", "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": "신규 ID", "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": "새 보관함 ID 항목 추가, 새 창에서 열립니다", "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": { - "message": "Turn on" + "message": "켜기" }, "ignore": { - "message": "Ignore" + "message": "무시하기" }, "importData": { - "message": "Import data", + "message": "데이터 가져오기", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "가져오기 오류" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "가져오려고 하는 데이터에 문제가 있습니다. 아래에 표시된 파일의 오류를 해결한 뒤 다시 시도해 주세요." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "아래 오류를 해결하고 다시 시도하세요." }, "description": { - "message": "Description" + "message": "설명" }, "importSuccess": { - "message": "Data successfully imported" + "message": "데이터 가져오기 성공" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "총 $AMOUNT$개의 항목을 가져왔습니다.", "placeholders": { "amount": { "content": "$1", @@ -3607,43 +3600,43 @@ "message": "다시 시도" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "이 작업을 수행하려면 증명이 필요합니다. 계속하려면 PIN을 설정하세요." }, "setPin": { - "message": "Set PIN" + "message": "PIN 설정하기" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "생체 인식을 사용하여 증명하기" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "확인 대기 중" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "생체 인식을 완료할 수 없습니다." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "다른 방법이 필요하신가요?" }, "useMasterPassword": { - "message": "Use master password" + "message": "마스터 비밀번호를 사용하기" }, "usePin": { - "message": "Use PIN" + "message": "PIN 사용하기" }, "useBiometrics": { - "message": "Use biometrics" + "message": "생체 인식 사용하기" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "이메일로 전송된 인증 코드를 입력해주세요" }, "resendCode": { - "message": "Resend code" + "message": "코드 재전송" }, "total": { - "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", @@ -3652,19 +3645,19 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Duo 서비스 연결 중 오류가 발생했습니다. 다른 2단계 로그인 방법을 사용하거나 Duo에 문의하여 도움을 받으세요." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "듀오를 실행하고 단계를 따라 로그인을 완료하세요" }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "계정에 Duo 2단계 로그인이 필요합니다." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "확장 프로그램을 실행하여 로그인을 완료합니다." }, "popoutExtension": { - "message": "Popout extension" + "message": "확장 프로그램을 새 창에서 열기" }, "launchDuo": { "message": "Duo 실행" @@ -3676,25 +3669,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": "Select a folder" + "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": { @@ -3704,25 +3697,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": { @@ -3732,22 +3725,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": "패스키" }, "accessing": { - "message": "Accessing" + "message": "접근 중" + }, + "loggedInExclamation": { + "message": "로그인 완료!" }, "passkeyNotCopied": { "message": "패스키가 복사되지 않습니다" @@ -3759,7 +3755,7 @@ "message": "사이트에서 인증을 요구합니다. 이 기능은 비밀번호가 없는 계정에서는 아직 지원하지 않습니다." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "패스키로 로그인하시겠어요?" }, "passkeyAlreadyExists": { "message": "이미 이 애플리케이션에 해당하는 패스키가 있습니다." @@ -3768,16 +3764,16 @@ "message": "이 애플리케이션에 대한 패스키를 찾을 수 없습니다." }, "noMatchingPasskeyLogin": { - "message": "사이트와 일치하는 로그인이 없습니다." + "message": "이 사이트와 일치하는 로그인이 없습니다." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "사이트와 일치하는 로그인 없음" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "패스키를 새 로그인으로 검색 또는 저장" }, "confirm": { - "message": "Confirm" + "message": "확인" }, "savePasskey": { "message": "패스키 저장" @@ -3786,10 +3782,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": "패스키 항목" @@ -3801,128 +3797,128 @@ "message": "이 항목은 이미 패스키가 있습니다. 정말로 현재 패스키를 덮어쓰시겠어요?" }, "featureNotSupported": { - "message": "Feature not yet supported" + "message": "아직 지원되지 않는 기능" }, "yourPasskeyIsLocked": { "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": "올바르지 않은 PIN입니다." }, "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": "LastPass 계정과 연결된 YubiKey를 컴퓨터의 USB 포트에 삽입한 다음 버튼을 누릅니다." }, "switchAccount": { - "message": "Switch account" + "message": "계정 전환" }, "switchAccounts": { - "message": "Switch accounts" + "message": "계정 전환" }, "switchToAccount": { - "message": "Switch to account" + "message": "계정 전환" }, "activeAccount": { - "message": "Active account" + "message": "계정 활성화" }, "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", @@ -3931,31 +3927,31 @@ } }, "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": { @@ -3963,7 +3959,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": "이 옵션을 무시하면 Bitwarden 자동 완성 제안과 브라우저 간에 충돌이 발생할 수 있습니다", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { @@ -3971,39 +3967,39 @@ "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": "패스키 제거" @@ -4012,22 +4008,22 @@ "message": "패스키 제거됨" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "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": { @@ -4037,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "메모 복사 - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4047,7 +4043,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": { @@ -4057,7 +4053,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": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "항목 보기 - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4077,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "자동 완성 - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4087,22 +4083,22 @@ } }, "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": "계정 보안" @@ -4114,13 +4110,13 @@ "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": { @@ -4130,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "다시 $NAME$로 돌아가기", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4140,10 +4136,10 @@ } }, "new": { - "message": "New" + "message": "새 항목" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ 제거", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4153,16 +4149,16 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "폴더가 없는 항목" }, "itemDetails": { - "message": "Item details" + "message": "항목 세부사항" }, "itemName": { - "message": "Item name" + "message": "항목 이름" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "보기 권한만 있는 컬렉션은 제거할 수 없습니다: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -4171,47 +4167,47 @@ } }, "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": "최대 파일 크기는 500MB입니다." }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "첨부파일 $NAME$ 삭제", "placeholders": { "name": { "content": "$1", @@ -4220,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "$NAME$ 다운로드", "placeholders": { "name": { "content": "$1", @@ -4229,28 +4225,43 @@ } }, "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": "보관함 필터링" + }, + "filterApplied": { + "message": "필터 1개가 적용되었습니다" + }, + "filterAppliedPlural": { + "message": "$COUNT$개의 필터가 적용되었습니다", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } }, "personalDetails": { - "message": "Personal details" + "message": "개인 정보" }, "identification": { - "message": "Identification" + "message": "본인 확인" }, "contactInfo": { - "message": "Contact info" + "message": "연락처 정보" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "다운로드 - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4259,23 +4270,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": "웹사이트 (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "웹사이트 (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4285,16 +4296,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": { @@ -4304,7 +4315,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "$WEBSITE$ 일치 인식 보이기", "placeholders": { "website": { "content": "$1", @@ -4313,7 +4324,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "$WEBSITE$ 일치 인식 숨기기", "placeholders": { "website": { "content": "$1", @@ -4322,19 +4333,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", @@ -4343,43 +4354,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", @@ -4391,37 +4402,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 ID, 이름, aria-label 또는 플레이스홀더를 입력하세요" }, "editField": { - "message": "Edit field" + "message": "필드 편집" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "$LABEL$ 편집", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "$LABEL$ 삭제", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ 추가됨", "placeholders": { "label": { "content": "$1", @@ -4448,7 +4459,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "$LABEL$을 재정렬합니다. 화살표 키를 사용하여 항목을 위나 아래로 이동할 수 있습니다.", "placeholders": { "label": { "content": "$1", @@ -4457,7 +4468,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$을 위로 이동했습니다. 위치: $INDEX$ / $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4474,13 +4485,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": "1개 항목이 선택한 조직으로 영구적으로 전송됩니다. 더 이상 이 항목을 소유하지 않습니다." }, "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", @@ -4489,7 +4500,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1개 항목이 $ORG$으로 영구적으로 전송됩니다. 더 이상 이 항목을 소유하지 않습니다.", "placeholders": { "org": { "content": "$1", @@ -4498,7 +4509,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", @@ -4511,13 +4522,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "성공적으로 컬렉션을 할당했습니다" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "아무것도 선택하지 않았습니다." }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "선택한 항목이 $ORGNAME$(으)로 이동됨", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4537,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "항목들이 $ORGNAME$로 이동했습니다", "placeholders": { "orgname": { "content": "$1", @@ -4535,7 +4546,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "항목이 $ORGNAME$로 이동했습니다", "placeholders": { "orgname": { "content": "$1", @@ -4544,7 +4555,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$을 아래로 이동했습니다. 위치: $INDEX$ / $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4561,231 +4572,336 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "항목 위치" }, "fileSend": { - "message": "File Send" + "message": "파일 Send" }, "fileSends": { - "message": "File Sends" + "message": "파일 Send" }, "textSend": { - "message": "Text Send" + "message": "텍스트 Send" }, "textSends": { - "message": "Text Sends" + "message": "텍스트 Send" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden이 새로운 모습으로 돌아왔습니다!" }, "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "보관함 탭에서 자동 완성하고 검색하는 것이 그 어느 때보다 쉽고 직관적입니다. 둘러보세요!" }, "accountActions": { - "message": "Account actions" + "message": "계정 작업" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "확장 아이콘에 로그인 자동 완성 제안 수 표시" + }, + "showQuickCopyActions": { + "message": "보관함에서 빠른 복사 기능 표시" }, "systemDefault": { - "message": "System default" + "message": "시스템 기본 설정" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "기업 정책에 따른 요구사항들이 옵션들에 적용되었습니다." + }, + "sshPrivateKey": { + "message": "개인 키" + }, + "sshPublicKey": { + "message": "공개 키" + }, + "sshFingerprint": { + "message": "지문" + }, + "sshKeyAlgorithm": { + "message": "키 유형" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "재시도" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "최소 사용자 지정 시간 초과는 1분입니다." }, "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": "삭제한 항목은 여기에 표시되며 30일 후 영구적으로 삭제됩니다." }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "30일 이상 휴지통에 보관된 항목은 자동으로 삭제됩니다." }, "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." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." }, "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?", + "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": "컴팩트 모드\n" + }, + "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": "확장 폭" + }, + "wide": { + "message": "넓게" + }, + "extraWide": { + "message": "매우 넓게" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 07d5b3bf8c3..3c81df00f10 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -14,7 +14,7 @@ "message": "Prisijunkite arba sukurkite naują paskyrą, kad galėtumėte pasiekti saugyklą." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Kvietimas priimtas" }, "createAccount": { "message": "Sukurti paskyrą" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Tapatybės automatinis užpildymas" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Sukurti slaptažodį (nukopijuotas)" }, @@ -438,9 +454,6 @@ "length": { "message": "Ilgis" }, - "passwordMinLength": { - "message": "Minimalus slaptažodžio ilgis" - }, "uppercase": { "message": "Didžiosiomis (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mažiausiai simbolių" }, - "avoidAmbChar": { - "message": "Vengti dviprasmiškų simbolių", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -567,7 +576,7 @@ "message": "Pastabos" }, "privateNote": { - "message": "Private note" + "message": "Privati pastaba" }, "note": { "message": "Pastaba" @@ -632,9 +641,6 @@ "rateExtension": { "message": "Įvertinkite šį plėtinį" }, - "rateExtensionDesc": { - "message": "Apsvarstykite galimybę mums padėti palikdami gerą atsiliepimą!" - }, "browserNotSupportClipboard": { "message": "Jūsų žiniatinklio naršyklė nepalaiko automatinio kopijavimo. Vietoj to nukopijuokite rankiniu būdu." }, @@ -730,16 +736,16 @@ "message": "Apsauga" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Patvirtinkite pagrindinį slaptažodį" }, "masterPassword": { - "message": "Master password" + "message": "Pagrindinis slaptažodis" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Pagrindinio slaptažodžio negalima atkurti, jei jį pamiršite." }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Pagrindinio slaptažodžio užuomina" }, "errorOccurred": { "message": "Įvyko klaida" @@ -791,7 +797,7 @@ "message": "Būtinas patvirtinimo kodas." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "Tapatybės nustatymas buvo atšauktas arba užtruko per ilgai. Bandykite dar kartą." }, "invalidVerificationCode": { "message": "Neteisingas patvirtinimo kodas" @@ -843,7 +849,7 @@ "message": "Sesijos laikas baigėsi." }, "logIn": { - "message": "Log in" + "message": "Prisijungti" }, "logInToBitwarden": { "message": "Log in to Bitwarden" @@ -852,7 +858,7 @@ "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Nebegaliojanti nuoroda" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -928,7 +934,7 @@ "message": "Naujas URI" }, "addDomain": { - "message": "Add domain", + "message": "Pridėti domeną", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Pateikti tapatybės elementų skirtuko puslapyje, kad būtų lengva automatiškai užpildyti." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Išvalyti iškarpinę", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ĮSPĖJIMAS", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Patvirtinti saugyklos eksportą" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Perkelti į organizaciją" }, - "share": { - "message": "Bendrinti" - }, "movedItemToOrg": { "message": "$ITEMNAME$ perkelta(s) į $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Įvesk 6 skaitmenų patvirtinimo kodą iš tavo autentifikavimo aplikacijos." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Įvesk 6 skaitmenų prisijungimo kodą, kuris buvo išsiųstas $EMAIL$ el. paštu.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Jei aptikta prisijungimo forma, automatiškai užpildyti, kai kraunamas tinklalapis." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Pažeistos arba nepatikimos svetainės gali išnaudoti automatinį užpildymą įkeliant puslapį." }, @@ -1580,7 +1583,7 @@ "message": "Taip/Ne" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Žymimasis langelis" }, "cfTypeLinked": { "message": "Susieta", @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Tapatybė" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "Naujas $TYPE$", "placeholders": { @@ -1783,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Peržiūrėti $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Saugūs užrašai" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Išvalyti", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Klonuoti" }, - "passwordGeneratorPolicyInEffect": { - "message": "Viena ar daugiau organizacijos politikų turi įtakos Jūsų generatoriaus nustatymams." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2315,9 +2321,12 @@ "message": "Organizacijos politika blokavo elementų importavimą į Jūsų individualią saugyklą." }, "domainsTitle": { - "message": "Domains", + "message": "Domenai", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Išskirti domenai" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "„Bitwarden“ neprašys išsaugoti prisijungimo detalių šiems domenams, visose prisijungusiose paskyrose. Turite atnaujinti puslapį, kad pokyčiai pradėtų galioti." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, "limitSendViews": { - "message": "Limit views" + "message": "Riboti peržiūrų" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Pasiekus ribą, niekas negali peržiūrėti šį „Send“.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "Liko $ACCESSCOUNT$ peržiūrų", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Ieškoti „Sends“", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Pridėti „Send“", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekstas" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Pasiektas maksimalus prisijungimų skaičius", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Nebegalioja" }, - "pendingDeletion": { - "message": "Laukiama ištrynimo" - }, "passwordProtected": { "message": "Apsaugota slaptažodžiu" }, @@ -2456,24 +2462,9 @@ "message": "Redaguoti „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Kokio tai tipo „Send“?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Draugiškas pavadinimas, apibūdinantis šį „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Failas, kurį norite siųsti." - }, "deletionDate": { "message": "Ištrynimo data" }, - "deletionDateDesc": { - "message": "Nurodytos datos ir laiko metu „Send“ bus bus ištrinta visam laikui.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Galiojimo data" }, - "expirationDateDesc": { - "message": "Jei nustatyta, prieiga prie šio „Send“ nustos galioti nurodyta data ir laikui.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 d" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Pasirinktinis" }, - "maximumAccessCount": { - "message": "Maksimalus prisijungimų skaičius" - }, - "maximumAccessCountDesc": { - "message": "Jei nustatyta, vartotojai nebegalės pasiekti šio „Send“, kai bus pasiektas maksimalus prisijungimų skaičius.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Pasirinktinai reikalauti slaptažodžio, kad vartotojai galėtų pasiekti šį „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Asmeninės pastabos apie šį „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Išjunkite šį „Send“, kad niekas negalėtų jo pasiekti.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Išsaugant šį „Send“ nukopijuokite nuorodą į mainų sritį.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Tekstas, kurį norite siųsti." - }, - "sendHideText": { - "message": "Pagal numatytuosius nustatymus slėpti šį „Send“ tekstą.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Dabartinis prisijungimų skaičius" - }, "createSend": { "message": "Naujas Siuntinys", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Prieš pradedant" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Norint pasinaudoti kalendoriaus stiliaus datos pasirinkikliu", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "spauskite čia", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "iškelti atskirame lange.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Nurodytas galiojimo laikas negalioja." }, @@ -2646,14 +2588,8 @@ "dateParsingError": { "message": "Išsaugant jūsų ištrynimo ir galiojimo datas įvyko klaida." }, - "hideEmail": { - "message": "Slėpti mano el. pašto adresą nuo gavėjų." - }, "hideYourEmail": { - "message": "Hide your email address from viewers." - }, - "sendOptionsPolicyInEffect": { - "message": "Viena ar daugiau organizacijos politikų turi įtakos Jūsų „Send“ nustatymams." + "message": "Slėpkite savo el. pašto adresą nuo žiūrėtojų." }, "passwordPrompt": { "message": "Iš naujo prašoma pagrindinio slaptažodžio" @@ -2668,7 +2604,7 @@ "message": "Reikalingas elektroninio pašto patvirtinimas" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Patvirtintas el. paštas" }, "emailVerificationRequiredDesc": { "message": "Turite patvirtinti savo el. paštą, kad galėtumėte naudotis šia funkcija. Savo el. pašto adresą galite patvirtinti žiniatinklio saugykloje." @@ -2868,8 +2804,19 @@ "error": { "message": "Klaida" }, - "regenerateUsername": { - "message": "Pergeneruoti vartotojo vardą iš naujo" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generuoti vartotojo vardą" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Vartotojo prisijungimo vardo tipas" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plius adresuotas el. paštas", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Svetainės pavadinimas" }, - "whatWouldYouLikeToGenerate": { - "message": "Ką norėtumėte sugeneruoti?" - }, - "passwordType": { - "message": "Slaptažodžio tipas" - }, "service": { "message": "Paslauga" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Iš naujo siųsti pranešimą" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Pradėtas prisijungimas" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Įrenginio patvirtinimas reikalingas. Pasirink patvirtinimo būdą toliau:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Prisiminti šį įrenginį" }, @@ -3313,15 +3295,18 @@ "userEmailMissing": { "message": "Trūksta naudotojo el. pašto" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Patikimas įrenginys" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "Nėra aktyvų „Sends“", "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.", + "message": "Naudokite „Send“, kad saugiai bendrintumėte užšifruotą informaciją su bet kuriuo asmeniu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3518,9 +3503,17 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "Atrakinti savo paskyrą, atidaromas naujame lange", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Užpildykite prisijungimo duomenis", "description": "Screen reader text for when overlay item is in focused" @@ -3546,7 +3539,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": "Pridėti naują saugyklos prisijungimo elementą, atidaromas naujame lange", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { @@ -3554,15 +3547,15 @@ "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": "Pridėti naują saugyklos kortelės elementą, atidaromas naujame lange", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "Nauja tapatybė", "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": "Pridėti naują saugyklos tapatybės elementą, atidaromas naujame lange", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3652,7 +3645,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Klaida prijungiant su „Duo“ paslauga. Naudokite kitą dvigubo prisijungimo būdą arba susisiekite su „Duo“ dėl pagalbos." }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Paleisk DUO ir sek veiksmus, kad baigtum prisijungti." @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4090,7 +4086,7 @@ "message": "No values to copy" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Priskirti į kolekcijas" }, "copyEmail": { "message": "Copy email" @@ -4184,22 +4180,22 @@ "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." }, "additionalInformation": { - "message": "Additional information" + "message": "Papildoma informacija" }, "itemHistory": { - "message": "Item history" + "message": "Elemento istorija" }, "lastEdited": { - "message": "Last edited" + "message": "Paskutinį kartą redaguota" }, "ownerYou": { - "message": "Owner: You" + "message": "Savininkas: Jūs" }, "linked": { - "message": "Linked" + "message": "Susieta" }, "copySuccessful": { - "message": "Copy Successful" + "message": "Kopijavimas sėkmingas" }, "upload": { "message": "Įkelti" @@ -4240,17 +4236,32 @@ "filters": { "message": "Filtrai" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { - "message": "Personal details" + "message": "Asmeniniai duomenys" }, "identification": { - "message": "Identification" + "message": "Identifikavimas" }, "contactInfo": { - "message": "Contact info" + "message": "Kontaktinė informacija" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Atsisiųsti – $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4259,14 +4270,14 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "kortelės numeris baigiasi su", "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": "Prisijungimo kredencialai" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Autentifikatoriaus raktas" }, "autofillOptions": { "message": "Autofill options" @@ -4352,10 +4363,10 @@ "message": "Pridėti paskyrą" }, "loading": { - "message": "Loading" + "message": "Įkeliama" }, "data": { - "message": "Data" + "message": "Duomenys" }, "passkeys": { "message": "Passkeys", @@ -4370,7 +4381,7 @@ "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "Priskirti" }, "bulkCollectionAssignmentDialogDescriptionSingular": { "message": "Only organization members with access to these collections will be able to see the item." @@ -4391,25 +4402,25 @@ } }, "addField": { - "message": "Add field" + "message": "Pridėti lauką" }, "add": { - "message": "Add" + "message": "Pridėti" }, "fieldType": { - "message": "Field type" + "message": "Lauko tipas" }, "fieldLabel": { - "message": "Field label" + "message": "Lauko etiketė" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Naudokite tekstinius laukus duomenims, pavyzdžiui, saugumo klausimams" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Naudokite paslėptus laukus slaptiems duomenims, pavyzdžiui, slaptažodžiams" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Naudokite žymimuosius langelius, jei norite automatiškai užpildyti formos žymimąjį langelį, pavyzdžiui, prisiminti el. paštą" }, "linkedHelpText": { "message": "Use a linked field when you are experiencing autofill issues for a specific website." @@ -4418,10 +4429,10 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "editField": { - "message": "Edit field" + "message": "Redaguoti lauką" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Redaguoti $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Ištrinti $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "Pridėtas $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4561,7 +4572,7 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "Elemento vieta" }, "fileSend": { "message": "File Send" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 870c1e7bef7..fc682ced389 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Ievietot licences numuru starpliktuvē" }, + "copyPrivateKey": { + "message": "Ievietot starpliktuvē privāto atslēgu" + }, + "copyPublicKey": { + "message": "Ievietot starpliktuvē publisko atslēgu" + }, + "copyFingerprint": { + "message": "Ievietot starpliktuvē pirkstu nospiedumu" + }, "copyCustomField": { "message": "Ievietot $FIELD$ starpliktuvē", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Automātiski aizpildīt identitāti" }, + "fillVerificationCode": { + "message": "Aizpildīt apliecinājuma kodu" + }, + "fillVerificationCodeAria": { + "message": "Aizpildīt apliecinājuma kodu", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Izveidot paroli (tiks ievietota starpliktuvē)" }, @@ -438,9 +454,6 @@ "length": { "message": "Garums" }, - "passwordMinLength": { - "message": "Mazākais pieļaujamais paroles garums" - }, "uppercase": { "message": "Lielie burti (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mazākais pieļaujamais īpašo rakstzīmju skaits" }, - "avoidAmbChar": { - "message": "Izvairīties no viegli sajaucamām rakstzīmēm", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Izvairīties no viegli sajaucamām rakstzīmēm", "description": "Label for the avoid ambiguous characters checkbox." @@ -591,7 +600,7 @@ "message": "Atvērt tīmekļvietni" }, "launchWebsiteName": { - "message": "Palaist tīmekļvietni $ITEMNAME$", + "message": "Atvērt tīmekļvietni $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "Novērtēt paplašinājumu" }, - "rateExtensionDesc": { - "message": "Lūgums apsvērt palīdzēt mums ar labu atsauksmi." - }, "browserNotSupportClipboard": { "message": "Pārlūks neatbalsta vienkāršo ievietošanu starpliktuvē. Tā vietā tas jāievieto starpliktuvē pašrocīgi." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Attēlot identitātes ciļņu lapā vieglākai aizpildei." }, + "clickToAutofillOnVault": { + "message": "Glabātavas skatā jāklikšķina uz vienumiem, lai automātiski aizpildītu" + }, "clearClipboard": { "message": "Notīrīt starpliktuvi", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "UZMANĪBU", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Brīdinājums", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Apstiprināt glabātavas satura izgūšanu" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Pārvietot uz apvienību" }, - "share": { - "message": "Kopīgot" - }, "movedItemToOrg": { "message": "$ITEMNAME$ pārvietots uz $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Jāievada 6 ciparu apstiprinājuma kods no autentificētāja lietotnes." }, + "authenticationTimeout": { + "message": "Autentificēšanās noildze" + }, + "authenticationSessionTimedOut": { + "message": "Iestājās autentificēšanās sesijas noildze. Lūgums sākt pieteikšanos no jauna." + }, "enterVerificationCodeEmail": { "message": "Jāievada 6 ciparu apstiprinājuma kods, kas tika nosūtīts uz $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Ja tiek noteikta pieteikšanās veidne, tā tiks aizpildīta lapas ielādes brīdī." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Brīdinājums:$CLOSETAG$ pārveidotās vai neuzticamās tīmekļvietnēs automātiskā aizpilde lapas ielādes laikā var tikt ļaunprātīgi izmantota.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Pārveidotās vai neuzticamās tīmekļvietnēs automātiskā aizpilde lapas ielādes laikā var tikt ļaunprātīgi izmantota." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitāte" }, + "typeSshKey": { + "message": "SSH atslēga" + }, "newItemHeader": { "message": "Jauns/a $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Drošās piezīmes" }, + "sshKeys": { + "message": "SSH atslēgas" + }, "clear": { "message": "Notīrīt", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Pavairot" }, - "passwordGeneratorPolicyInEffect": { - "message": "Viens vai vairāki apvienības nosacījumi ietekmē veidotāja iestatījumus." - }, "passwordGenerator": { "message": "Paroļu veidotājs" }, @@ -2318,6 +2324,9 @@ "message": "Domēna vārdi", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Liegtie domēna vārdi" + }, "excludedDomains": { "message": "Izņēmuma domēni" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nevaicās saglabāt pieteikšanās datus visiem šī domēna kontiem, kuri ir pieteikušies. Ir jāpārlādē lapa, lai iedarbotos izmaiņas." }, + "blockedDomainsDesc": { + "message": "Automātiskā aizpilde un citas saistītās iespējas šajās tīmekļvietnēs netiks piedāvātas. Ir jāatsvaidzina lapa, lai izmaiņas iedarbotos." + }, + "autofillBlockedNotice": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo pārskatīt vai mainīt var iestatījumos." + }, + "autofillBlockedTooltip": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo var pārskatīt iestatījumos." + }, "websiteItemLabel": { "message": "Tīmekļvietne $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Liegtā domēna vārda izmaiņas sglabātas" + }, "excludedDomainsSavedSuccess": { "message": "Saglabātas vērā neņemto domēna vārdu izmaiņas" }, @@ -2373,14 +2394,6 @@ "message": "Informācija par Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Meklēt Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Pievienot Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Teksts" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Pēc noklusējuma paslēpt tekstu" }, - "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." - }, "expired": { "message": "Beidzies izmantošanas laiks" }, - "pendingDeletion": { - "message": "Gaida dzēšanu" - }, "passwordProtected": { "message": "Aizsargāts ar paroli" }, @@ -2456,24 +2462,9 @@ "message": "Labot Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Kāds ir šī Send veids?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Lasāms nosaukums, kas apraksta šo Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Datne, kuru ir vēlme nosūtīt." - }, "deletionDate": { "message": "Dzēšanas datums" }, - "deletionDateDesc": { - "message": "Send tiks neatgriezeniski izdzēsts norādītajā datumā un laikā.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send šajā datumā tiks neatgriezeniski izdzēsts.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Derīguma beigu datums" }, - "expirationDateDesc": { - "message": "Ja iestatīts, piekļuve šim Send beigsies norādītajā datumā un laikā.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 diena" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Pielāgots" }, - "maximumAccessCount": { - "message": "Lielākais pieļaujamais piekļuves reižu skaits" - }, - "maximumAccessCountDesc": { - "message": "Ja iestatīts, lietotāji nevarēs piekļūt šim Send, kad tiks sasniegts lielākais pieļaujamais piekļūšanas reižu skaits.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Pēc izvēles pieprasīt paroli, lai lietotāji varētu piekļūt šim Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Pēc izvēles pievieno paroli, lai saņēmēji varētu piekļūt šim Send!", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Personīgas piezīmes par šo Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Izslēgt šo Send, lai neviens tam nevarētu piekļūt.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Saglabāšanas brīdī ievietot šī Send saiti starpliktuvē.", + "message": "Pēc izvēles var pievienot paroli, lai saņēmēji varētu piekļūt šim Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTextDesc": { - "message": "Teksts, kuru ir vēlme nosūtīt." - }, - "sendHideText": { - "message": "Pēc noklusējuma paslēpt šī Send tekstu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Pašreizējais piekļuvju skaits" - }, "createSend": { "message": "Jauns Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Pirms sākšanas" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Lai izmantotu kalendāra veida datumu atlasītāju,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klikšķināt šeit", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": ", lai atvērtu jaunā logā.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Norādītais derīguma beigu datums nav derīgs." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Atgadījusies kļūda dzēšanas un derīguma beigu datumu saglabāšanā." }, - "hideEmail": { - "message": "Slēpt e-pasta adresi no saņēmējiem." - }, "hideYourEmail": { "message": "Paslēpt e-pasta adresi no apskatītājiem." }, - "sendOptionsPolicyInEffect": { - "message": "Viens vai vairāki apvienības nosacījumi ietekmē Send iespējas." - }, "passwordPrompt": { "message": "Galvenās paroles pārvaicāšana" }, @@ -2868,17 +2804,28 @@ "error": { "message": "Kļūda" }, - "regenerateUsername": { - "message": "Pārizveidot lietotājvārdu" + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Izveidot lietotājvārdu" }, "generateEmail": { - "message": "Izveidot e-pastu" + "message": "Izveidot e-pasta adresi" }, - "generatorBoundariesHint": { - "message": "Vērtībai jābūt starp $MIN$ un $MAX$", + "spinboxBoundariesHint": { + "message": "Vērtībai jābūt starp $MIN$ un $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Lietotājvārda veids" + "passwordLengthRecommendationHint": { + "message": " Jāizmanto $RECOMMENDED$ vai vairāk rakstzīmju, la izveidotu spēcīgu paroli.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Jāizmanto $RECOMMENDED$ vai vairāk vārdu, lai aizveidotu spēcīgu paroles vārdkopu.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-pasta adrese ar plusu", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Tīmekļvietnes nosaukums" }, - "whatWouldYouLikeToGenerate": { - "message": "Ko ir nepieciešams izveidot?" - }, - "passwordType": { - "message": "Paroles veids" - }, "service": { "message": "Pakalpojums" }, @@ -2929,7 +2887,7 @@ "message": "Pārvirzīto e-pastu aizstājvārds" }, "forwardedEmailDesc": { - "message": "Izveidot e-pastu aizstājvārdu ar ārēju pārvirzīšanas pakalpojumu." + "message": "Izveidot e-pasta aizstājadresi ar ārēju pārvirzīšanas pakalpojumu." }, "forwarderDomainName": { "message": "E-pasta domēns", @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Atkārtoti nosūtīt paziņojumu" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Skatīt visas pieteikšanās iespējas" + }, + "viewAllLoginOptionsV1": { "message": "Skatīt visas pieteikšanās iespējas" }, "notificationSentDevice": { "message": "Uz ierīci ir nosūtīts paziņojums." }, + "aNotificationWasSentToYourDevice": { + "message": "Uz ierīci tika nosūtīts paziņojums" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lūgums pārliecināties, ka konts ir atslēgts un atpazīšanas vārdkopa ir tāda pati arī otrā ierīcē" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Tiks paziņots, tiklīdz pieprasījums būs apstiprināts" + }, + "needAnotherOptionV1": { + "message": "Nepieciešama cita iespēja?" + }, "loginInitiated": { "message": "Uzsākta pieteikšanās" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Atver jaunā logā" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Atcerēties šo ierīci, lai nākotnes pieteikšanos padarītu plūdenāku" + }, "deviceApprovalRequired": { "message": "Nepieciešams ierīces apstiprinājums. Zemāk jāatlasa apstiprinājuma iespēja:" }, + "deviceApprovalRequiredV2": { + "message": "Nepieciešama ierīces apstiprināšana" + }, + "selectAnApprovalOptionBelow": { + "message": "Zemāk jāatlasa apstiprināšnas iespēja" + }, "rememberThisDevice": { "message": "Atcerēties šo ierīci" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Trūkst lietotāja e-pasta adreses" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktīva lietotāja e-pasta adrese netika atrasta. Notiek atteikšanās." + }, "deviceTrusted": { "message": "Ierīce ir uzticama" }, @@ -3521,6 +3506,14 @@ "message": "Atslēgt savu kontu, tiks atvērts jaunā logā", "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", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Atlikušais laiks, pirms beigsies pašreizējā TOTP derīgums", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Aizpildīt pieteikšanās datus", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Piekļūst" }, + "loggedInExclamation": { + "message": "Pieteicies." + }, "passkeyNotCopied": { "message": "Piekļuves atslēga netiks ievietota starpliktuvē" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Atlases" }, + "filterVault": { + "message": "Atlasīt glabātavu" + }, + "filterApplied": { + "message": "Pielietots viens atlasītājs" + }, + "filterAppliedPlural": { + "message": "Pielietoti $COUNT$ atlasītāji", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personiskā informācija" }, @@ -4564,7 +4575,7 @@ "message": "Vienuma atrašanās vieta" }, "fileSend": { - "message": "Datnes Send" + "message": "Datņu Send" }, "fileSends": { "message": "Datņu Send" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Paplašinājuma ikonā rādīt pieteikšanās automātiskās aizpildes ieteikumu skaitu" }, + "showQuickCopyActions": { + "message": "Glabātavā rādīt ātrās kopēšanas darbības" + }, "systemDefault": { "message": "Sistēmas noklusējums" }, "enterprisePolicyRequirementsApplied": { "message": "Šim iestatījumam tika piemērotas uzņēmējdarbības nosacījumu prasības" }, + "sshPrivateKey": { + "message": "Privātā atslēga" + }, + "sshPublicKey": { + "message": "Publiskā atslēga" + }, + "sshFingerprint": { + "message": "Pirkstu nospiedums" + }, + "sshKeyAlgorithm": { + "message": "Atslēgas veids" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Mēģināt vēlreiz" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Nav nepieciešamo atļauju, lai labotu šo vienumu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Atslēgšana ar biometriju nav pieejama, jo Bitwarden darbvirsmas lietotne ir aizvērta." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authenticating": { "message": "Autentificē" }, @@ -4692,11 +4757,11 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Kreisās iekavas", + "message": "Kreisā iekava", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Labās iekavas", + "message": "Labā iekava", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Izveidotā parole" + }, + "compactMode": { + "message": "Cieši" + }, + "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" + }, + "wide": { + "message": "Plats" + }, + "extraWide": { + "message": "Ļoti plats" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 0dcc887200b..4cbbfc46d6a 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "പാസ്‌വേഡ് സൃഷ്ടിക്കുക (പകർത്തുക )" }, @@ -438,9 +454,6 @@ "length": { "message": "നീളം" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "കുറഞ്ഞ പ്രത്യേക പ്രതീകങ്ങൾ" }, - "avoidAmbChar": { - "message": "അവ്യക്തമായ പ്രതീകങ്ങൾ ഒഴിവാക്കുക", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "എക്സ്റ്റൻഷൻ റേറ്റ് ചെയ്യുക " }, - "rateExtensionDesc": { - "message": "ഒരു നല്ല അവലോകനത്തിന് ഞങ്ങളെ സഹായിക്കുന്നത് പരിഗണിക്കുക!" - }, "browserNotSupportClipboard": { "message": "നിങ്ങളുടെ ബ്രൌസർ എളുപ്പമുള്ള ക്ലിപ്പ്ബോർഡ് പകർത്തൽ പിന്തുണയ്ക്കത്തില്ല. പകരം അത് സ്വമേധയാ പകർക്കുക ." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "ക്ലിപ്ബോര്‍ഡ് മായ്ക്കുക", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "മുന്നറിയിപ്പ്", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "പങ്കിടുക" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ അപ്ലിക്കേഷനിൽ നിന്ന് 6 അക്ക സ്ഥിരീകരണ കോഡ് നൽകുക." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ൽ ഇമെയിൽ ചെയ്ത 6 അക്ക സ്ഥിരീകരണ കോഡ് നൽകുക", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "ഒരു ലോഗിൻ ഫോം കണ്ടെത്തിയാൽ, വെബ് പേജ് ലോഡുചെയ്യുമ്പോൾ യാന്ത്രികമായി ഒരു സ്വയം പൂരിപ്പിക്കൽ നടത്തുക." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "ഐഡന്റിറ്റി" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "സുരക്ഷാ കുറിപ്പുകൾ" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "മായ്ക്കുക", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "ക്ലോൺ" }, - "passwordGeneratorPolicyInEffect": { - "message": "ഒന്നോ അതിലധികമോ സംഘടന നയങ്ങൾ നിങ്ങളുടെ പാസ്സ്‌വേഡ് സൃഷ്ടാവിൻ്റെ ക്രമീകരണങ്ങളെ ബാധിക്കുന്നു" - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 150d3197ac6..cbb0b1bdf1a 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "लांबी" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "विस्तारकाचे मूल्यांकन करा" }, - "rateExtensionDesc": { - "message": "चांगला अभिप्राय देऊन आम्हाला मदत करा!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index b8075804229..e34751eea7d 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 9b647c650bf..3a12c9ae4f4 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -20,10 +20,10 @@ "message": "Opprett en konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Er du ny til Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med passnøkkel" }, "useSingleSignOn": { "message": "Use single sign-on" @@ -32,7 +32,7 @@ "message": "Velkommen tilbake" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -138,22 +138,31 @@ "message": "Kopier sikkerhetskoden" }, "copyName": { - "message": "Copy name" + "message": "Kopiér navn" }, "copyCompany": { "message": "Copy company" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopiér fødselsnummer" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopiér passnummer" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopiér lisensnummer" + }, + "copyPrivateKey": { + "message": "Kopiér privat nøkkel" + }, + "copyPublicKey": { + "message": "Kopiér offentlig nøkkel" + }, + "copyFingerprint": { + "message": "Kopiér fingeravtrykk" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopiér $FIELD$", "placeholders": { "field": { "content": "$1", @@ -162,10 +171,10 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopiér nettsted" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiér notater" }, "fill": { "message": "Fyll", @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Auto-utfyll identitet" }, + "fillVerificationCode": { + "message": "Fyll inn verifiseringskode" + }, + "fillVerificationCodeAria": { + "message": "Fyll inn verifiseringskode", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generer et passord (kopiert)" }, @@ -223,13 +239,13 @@ "message": "Legg til en gjenstand" }, "accountEmail": { - "message": "Account email" + "message": "Kontoens E-postadresse" }, "requestHint": { - "message": "Request hint" + "message": "Be om et hint" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" @@ -300,7 +316,7 @@ "message": "Logg ut" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Om Bitwarden" }, "about": { "message": "Om" @@ -309,7 +325,7 @@ "message": "More from Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Vil du fortsette til bitwarden.com?" }, "bitwardenForBusiness": { "message": "Bitwarden for Business" @@ -366,7 +382,7 @@ "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Ingen mapper er lagt til" }, "createFoldersToOrganize": { "message": "Create folders to organize your vault items" @@ -438,9 +454,6 @@ "length": { "message": "Lengde" }, - "passwordMinLength": { - "message": "Minimum passordlengde" - }, "uppercase": { "message": "Store bokstaver (A–Å)", "description": "deprecated. Use uppercaseLabel instead." @@ -462,7 +475,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkluder store bokstaver", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -470,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkluder små bokstaver", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -486,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkluder spesialtegn", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -512,12 +525,8 @@ "minSpecial": { "message": "Minste antall spesialtegn" }, - "avoidAmbChar": { - "message": "Unngå tvetydige tegn", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unngå forvekslingsbare tegn", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -555,19 +564,19 @@ "message": "Favoritt" }, "unfavorite": { - "message": "Unfavorite" + "message": "Fjern favorittstempel" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Gjenstand lagt til i favorittene" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Gjenstand fjernet fra favorittene" }, "notes": { "message": "Notater" }, "privateNote": { - "message": "Private note" + "message": "Privat notat" }, "note": { "message": "Notat" @@ -588,10 +597,10 @@ "message": "Åpne" }, "launchWebsite": { - "message": "Launch website" + "message": "Åpne nettstedet" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Åpne nettstedet $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "Gi denne utvidelsen en vurdering" }, - "rateExtensionDesc": { - "message": "Tenk gjerne på om du vil skrive en anmeldelse om oss!" - }, "browserNotSupportClipboard": { "message": "Nettleseren din støtter ikke kopiering til utklippstavlen på noe enkelt vis. Prøv å kopiere det manuelt i stedet." }, @@ -651,7 +657,7 @@ "message": "Your account is locked" }, "or": { - "message": "or" + "message": "eller" }, "unlock": { "message": "Lås opp" @@ -739,7 +745,7 @@ "message": "Your master password cannot be recovered if you forget it!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Få et hint om hovedpassordet" }, "errorOccurred": { "message": "En feil har oppstått" @@ -779,10 +785,10 @@ "message": "You have been logged in!" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du har vellykket logget inn" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Du kan lukke dette vinduet" }, "masterPassSent": { "message": "Vi har sendt deg en E-post med hintet til superpassordet." @@ -828,10 +834,10 @@ "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." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Lær mer om autentisering" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Kopier autentiseringsnøkkel (TOTP)" }, "loggedOut": { "message": "Logget av" @@ -846,19 +852,19 @@ "message": "Logg inn" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logg inn på Bitwarden" }, "restartRegistration": { "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Utløpt lenke" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du har kanskje allerede en konto" }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Vis identitetselementer på fanesiden for enkel auto-utfylling." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Tøm utklippstavlen", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1040,7 +1049,7 @@ "message": "Lås opp" }, "additionalOptions": { - "message": "Additional options" + "message": "Ekstra innstillinger" }, "enableContextMenuItem": { "message": "Vis alternativer for kontekstmeny" @@ -1104,7 +1113,7 @@ "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Eksporttype" }, "accountRestricted": { "message": "Account restricted" @@ -1116,6 +1125,10 @@ "message": "ADVARSEL", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Advarsel", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Bekreft eksport av hvelvet" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Flytt til organisasjon" }, - "share": { - "message": "Del" - }, "movedItemToOrg": { "message": "$ITEMNAME$ flyttet til $ORGNAME$", "placeholders": { @@ -1196,7 +1206,7 @@ "message": "Fil" }, "fileToShare": { - "message": "File to share" + "message": "Filen som skal deles" }, "selectFile": { "message": "Velg en fil." @@ -1232,7 +1242,7 @@ "message": "1 GB med kryptert fillagring for filvedlegg." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Nødtilgang." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Skriv inn den 6-sifrede verifiseringskoden som står på din autentiseringsapp." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Skriv inn den 6-sifrede verifiseringskoden som ble sendt til", "placeholders": { @@ -1397,7 +1413,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Skriv inn koden du har fått tilsendt på E-post." }, "selfHostedEnvironment": { "message": "Selvbetjent miljø" @@ -1450,7 +1466,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" @@ -1494,24 +1510,11 @@ "enableAutoFillOnPageLoadDesc": { "message": "Dersom et innloggingskjema blir oppdaget, utfør automatisk en auto-utfylling når nettstedet lastes inn." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Kompromitterte eller upålitelige nettsider kan utnytte auto-utfylling når du laster inn siden." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Lær mer om risikoer" }, "learnMoreAboutAutofill": { "message": "Lær mer om auto-utfylling" @@ -1764,8 +1767,11 @@ "typeIdentity": { "message": "Identitet" }, + "typeSshKey": { + "message": "SSH-nøkkel" + }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Ny $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1774,7 +1780,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Rediger $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1783,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Vis $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1795,7 +1801,7 @@ "message": "Passordhistorikk" }, "generatorHistory": { - "message": "Generator history" + "message": "Generatorhistorikk" }, "clearGeneratorHistoryTitle": { "message": "Clear generator history" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Sikre notiser" }, + "sshKeys": { + "message": "SSH-nøkler" + }, "clear": { "message": "Tøm", "description": "To clear something out. example: To clear browser history." @@ -2008,7 +2017,7 @@ "message": "Lås opp med biometri" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Lås opp med hovedpassord" }, "awaitDesktop": { "message": "Venter på bekreftelse fra skrivebordsprogrammet" @@ -2020,7 +2029,7 @@ "message": "Lås med hovedpassordet når du starter nettleseren på nytt" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Krev hovedpassord ved omstart av nettleseren" }, "selectOneCollection": { "message": "Du må velge minst én samling." @@ -2031,26 +2040,23 @@ "clone": { "message": "Klon" }, - "passwordGeneratorPolicyInEffect": { - "message": "En eller flere av virksomhetens regler påvirker generatorinnstillingene dine." - }, "passwordGenerator": { - "message": "Password generator" + "message": "Passordgenerator" }, "usernameGenerator": { - "message": "Username generator" + "message": "Brukernavngenerator" }, "useThisPassword": { "message": "Bruk dette passordet" }, "useThisUsername": { - "message": "Use this username" + "message": "Bruk dette brukernavnet" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Bruk denne generatoren", "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": { @@ -2090,7 +2096,7 @@ "message": "Gjenopprettet objekt" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Har du allerede en konto?" }, "vaultTimeoutLogOutConfirmation": { "message": "Hvis du logger ut, fjerner du all tilgang til hvelvet ditt og krever online godkjenning etter tidsavbrudd. Er du sikker på at du vil bruke denne innstillingen?" @@ -2102,7 +2108,7 @@ "message": "Autofyll og lagre" }, "fillAndSave": { - "message": "Fill and save" + "message": "Fyll og lagre" }, "autoFillSuccessAndSavedUri": { "message": "Autoutfylt objekt og lagret URI" @@ -2189,10 +2195,10 @@ "message": "Avslutt abonnement" }, "atAnyTime": { - "message": "at any time." + "message": "når som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ved å fortsette, samtykker du til" }, "and": { "message": "og" @@ -2318,6 +2324,9 @@ "message": "Domener", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokkerte domener" + }, "excludedDomains": { "message": "Ekskluderte domener" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,11 +2363,14 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, "limitSendViews": { - "message": "Limit views" + "message": "Begrens visninger" }, "limitSendViewsHint": { "message": "No one can view this Send after the limit is reached.", @@ -2373,19 +2394,11 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Søk i Send-ene", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Legg til Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Teksten som skal deles" }, "sendTypeFile": { "message": "Fil" @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Maksimalt antall tilganger nådd", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Utløpt" }, - "pendingDeletion": { - "message": "Venter på sletting" - }, "passwordProtected": { "message": "Passord beskyttet" }, @@ -2456,24 +2462,9 @@ "message": "Rediger Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Hvilken type Send er dette?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Et vennlig navn for å beskrive dette Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Filen du vil send." - }, "deletionDate": { "message": "Dato for sletting" }, - "deletionDateDesc": { - "message": "Send-en vil bli slettet permanent på den angitte dato og klokkeslett.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Utløpsdato" }, - "expirationDateDesc": { - "message": "Hvis satt, vil tilgang til denne Send gå ut på angitt dato og klokkeslett.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dag" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Egendefinert" }, - "maximumAccessCount": { - "message": "Maksimal antall tilganger" - }, - "maximumAccessCountDesc": { - "message": "Hvis satt, vil ikke brukere lenger ha tilgang til dette Send når maksimal antall tilgang er nådd.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Eventuelt krever et passord for brukere å få tilgang til denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notater om denne Send-en.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deaktiver denne Send-en, slik at ingen får tilgang til den.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopier denne Send-ens lenke til utklippstavlen når den har blitt lagret.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teksten du ønsker å sende." - }, - "sendHideText": { - "message": "Skjul denne Send-ens tekst som standard.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Antall nåværende tilganger" - }, "createSend": { "message": "Lag en ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Før du starter" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Hvis du vil bruke en kalenderstil-datovelger", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "kilkk her", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "å pope ut vinduet.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Utløpsdatoen angitt er ikke gyldig." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Det oppstod en feil ved lagring av slettingen og utløpsdatoene." }, - "hideEmail": { - "message": "Skjul min e-postadresse fra mottakere." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "En eller flere av virksomhetens regler påvirker generatorinnstillingene dine." - }, "passwordPrompt": { "message": "Forespørsel om hovedpassord på nytt" }, @@ -2698,7 +2634,7 @@ "message": "Velg mappe …" }, "noFoldersFound": { - "message": "No folders found", + "message": "Ingen mapper ble funnet", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2710,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "av $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2868,8 +2804,19 @@ "error": { "message": "Feil" }, - "regenerateUsername": { - "message": "Regenerer brukernavn" + "decryptionError": { + "message": "Dekrypteringsfeil" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generer brukernavn" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Brukernavntype" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Pluss-adressert e-post", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Navn på nettside" }, - "whatWouldYouLikeToGenerate": { - "message": "Hva vil du generere?" - }, - "passwordType": { - "message": "Passordtype" - }, "service": { "message": "Tjeneste" }, @@ -2932,7 +2890,7 @@ "message": "Generer et e-postalias med en ekstern videresendingstjeneste." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomene", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2954,11 +2912,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Nettsted: $WEBSITE$. Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -3002,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ugyldig $SERVICENAME$-domene.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Send varslingen på nytt" }, - "viewAllLoginOptions": { - "message": "Vis alle innloggingsalternativer" + "viewAllLogInOptions": { + "message": "Vis alle påloggingsalternativer" + }, + "viewAllLoginOptionsV1": { + "message": "Vis alle påloggingsalternativer" }, "notificationSentDevice": { "message": "Et varsel er sendt til enheten din." }, + "aNotificationWasSentToYourDevice": { + "message": "Et varsel ble sendt til enheten din" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Trenger du et annet alternativ?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Åpnes i et nytt vindu" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Enhetsgodkjennelse kreves" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Husk denne enheten" }, @@ -3259,7 +3241,7 @@ "message": "Organization SSO identifier is required." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Oppretter en konto på" }, "checkYourEmail": { "message": "Check your email" @@ -3277,7 +3259,7 @@ "message": "Gå tilbake" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "for å redigere E-postadressen din." }, "eu": { "message": "EU", @@ -3305,7 +3287,7 @@ "message": "Du vil bli varslet når det er godkjent." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Har du problemer med å logge inn?" }, "loginApproved": { "message": "Innlogging godkjent" @@ -3313,8 +3295,11 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enheten er betrodd" }, "sendsNoItemsTitle": { "message": "No active Sends", @@ -3465,7 +3450,7 @@ "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Feil under importering. Sjekk loggkonsollen for detaljer.", "description": "Notification message for when an import has failed." }, "importNetworkError": { @@ -3502,7 +3487,7 @@ "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "Bitwardens autoutfyllingsmeny", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3550,7 +3543,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nytt kort", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -3580,7 +3573,7 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Importeringsfeil" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -3744,13 +3737,16 @@ "message": "Vault data exported" }, "typePasskey": { - "message": "Passkey" + "message": "Passnøkkel" }, "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Innlogget!" + }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Passkoden vil ikke bli kopiert" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -3780,10 +3776,10 @@ "message": "Bekreft" }, "savePasskey": { - "message": "Save passkey" + "message": "Lagre passnøkkel" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Lagre passnøkkelen som en ny pålogging" }, "chooseCipherForPasskeySave": { "message": "Choose a login to save this passkey to" @@ -3846,7 +3842,7 @@ "message": "Approve the login request in your authentication app or enter a one-time passcode." }, "passcode": { - "message": "Passcode" + "message": "Passkode" }, "lastPassMasterPassword": { "message": "LastPass-hovedpassord" @@ -3886,7 +3882,7 @@ "message": "Bytt kontoer" }, "switchToAccount": { - "message": "Switch to account" + "message": "Bytt til konto" }, "activeAccount": { "message": "Aktiv konto" @@ -3907,13 +3903,13 @@ "message": "låst opp" }, "server": { - "message": "server" + "message": "tjener" }, "hostedAt": { "message": "betjent hos" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Bruk enhets- eller maskinvarenøkkel" }, "justOnce": { "message": "Kun én gang" @@ -3935,7 +3931,7 @@ "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Vil du fortsette til nettleserinnstillingene?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { @@ -3979,7 +3975,7 @@ "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": "Gjør det til standarden", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { @@ -3987,7 +3983,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Passordet ble lagret!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -3995,7 +3991,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Passordet ble oppdatert!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -4006,19 +4002,19 @@ "message": "Suksess" }, "removePasskey": { - "message": "Remove passkey" + "message": "Fjern passordnøkkel" }, "passkeyRemoved": { "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Hvelvet ditt er tomt" }, "noItemsMatchSearch": { "message": "No items match your search" @@ -4047,7 +4043,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Flere innstillinger, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4057,7 +4053,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Flere innstillinger - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Vis gjenstand - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4077,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "Autoutfyll - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4090,7 +4086,7 @@ "message": "No values to copy" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Legg til i samlinger" }, "copyEmail": { "message": "Copy email" @@ -4120,7 +4116,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vis gjenstander i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4130,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tilbake til $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4143,7 +4139,7 @@ "message": "Ny" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Fjern $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4153,13 +4149,13 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Gjenstander uten mappe" }, "itemDetails": { - "message": "Item details" + "message": "Gjenstandens detaljer" }, "itemName": { - "message": "Item name" + "message": "Gjenstandens navn" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -4187,10 +4183,10 @@ "message": "Tilleggsinformasjon" }, "itemHistory": { - "message": "Item history" + "message": "Gjenstandshistorikk" }, "lastEdited": { - "message": "Last edited" + "message": "Nyligst redigert" }, "ownerYou": { "message": "Owner: You" @@ -4205,10 +4201,10 @@ "message": "Last opp" }, "addAttachment": { - "message": "Add attachment" + "message": "Legg til vedlegg" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Maksimal filstørrelse er 500 MB" }, "deleteAttachmentName": { "message": "Delete attachment $NAME$", @@ -4220,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "Last ned $NAME$", "placeholders": { "name": { "content": "$1", @@ -4240,17 +4236,32 @@ "filters": { "message": "Filtre" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "Ett filter er benyttet" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtre er benyttet", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { - "message": "Personal details" + "message": "Personlige detaljer" }, "identification": { - "message": "Identification" + "message": "Identifikasjon" }, "contactInfo": { "message": "Kontaktinfo" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Last ned - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4259,17 +4270,17 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "kortnummeret slutter med", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { "message": "Legitimasjoner for innlogging" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Autentiseringsnøkkel" }, "autofillOptions": { - "message": "Autofill options" + "message": "Autoutfyllings-innstillinger" }, "websiteUri": { "message": "Website (URI)" @@ -4285,7 +4296,7 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Nettsted lagt til" }, "addWebsite": { "message": "Legg til nettsted" @@ -4294,7 +4305,7 @@ "message": "Slett nettsted" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4325,16 +4336,16 @@ "message": "Autofill on page load?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Utløpt kort" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" }, "cardDetails": { - "message": "Card details" + "message": "Kortdetaljer" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$-detaljer", "placeholders": { "brand": { "content": "$1", @@ -4346,7 +4357,7 @@ "message": "Aktiver animasjoner" }, "showAnimations": { - "message": "Show animations" + "message": "Vis animasjoner" }, "addAccount": { "message": "Legg til konto" @@ -4358,15 +4369,15 @@ "message": "Data" }, "passkeys": { - "message": "Passkeys", + "message": "Passnøkler", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Passord", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Logg inn med passnøkkel", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { @@ -4391,16 +4402,16 @@ } }, "addField": { - "message": "Add field" + "message": "Legg til felt" }, "add": { "message": "Legg til" }, "fieldType": { - "message": "Field type" + "message": "Felttype" }, "fieldLabel": { - "message": "Field label" + "message": "Feltetikett" }, "textHelpText": { "message": "Use text fields for data like security questions" @@ -4430,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Slett $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ er lagt til", "placeholders": { "label": { "content": "$1", @@ -4561,7 +4572,7 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "Gjenstandens plassering" }, "fileSend": { "message": "File Send" @@ -4576,7 +4587,7 @@ "message": "Text Sends" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden har fått et nytt utseende!" }, "bitwardenNewLookDesc": { "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "Systemforvalg" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Privat nøkkel" + }, + "sshPublicKey": { + "message": "Offentlig nøkkel" + }, + "sshFingerprint": { + "message": "Fingeravtrykk" + }, + "sshKeyAlgorithm": { + "message": "Nøkkeltype" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Prøv igjen" }, @@ -4600,22 +4638,22 @@ "message": "Minimum custom timeout is 1 minute." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Ytterligere innhold er tilgjengelig" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "showCharacterCount": { - "message": "Show character count" + "message": "Vis tegntelleren" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Skjul tegntelleren" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Gjenstander i papirkurven" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Ingen gjenstander i papirkurven" }, "noItemsInTrashDesc": { "message": "Items you delete will appear here and be permanently deleted after 30 days" @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentiserer" }, @@ -4644,7 +4709,7 @@ "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Vil du lagre påloggingen i Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -4676,7 +4741,7 @@ "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Prosenttegn", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { @@ -4692,11 +4757,11 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Venstre parantes", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Høyre parantes", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { @@ -4704,11 +4769,11 @@ "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Bindestrek", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Plusstegn", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { @@ -4716,19 +4781,19 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Venstre krøllparentes", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Høyre krøllparentes", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Venstre firkantparantes", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Høyre firkantparantes", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { @@ -4736,7 +4801,7 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Skråstrek bakover", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { @@ -4748,11 +4813,11 @@ "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Hermetegn", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrofe", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { @@ -4760,7 +4825,7 @@ "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Større enn", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { @@ -4768,7 +4833,7 @@ "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Tidsperiode", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { @@ -4776,7 +4841,7 @@ "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Skråstrek", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { @@ -4786,6 +4851,57 @@ "message": "Store bokstaver" }, "generatedPassword": { - "message": "Generated password" + "message": "Generert passord" + }, + "compactMode": { + "message": "Kompakt modus" + }, + "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": "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": "Nei, det gjør jeg ikke" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Endre kontoens E-postadresse" + }, + "extensionWidth": { + "message": "Utvidelsens bredde" + }, + "wide": { + "message": "Bred" + }, + "extraWide": { + "message": "Ekstra bred" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index b8075804229..e34751eea7d 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 1361b657f24..0112ded1983 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kenteken kopiëren" }, + "copyPrivateKey": { + "message": "Privésleutel kopiëren" + }, + "copyPublicKey": { + "message": "Publieke sleutel kopiëren" + }, + "copyFingerprint": { + "message": "Vingerafdruk kopiëren" + }, "copyCustomField": { "message": "$FIELD$ kopiëren", "placeholders": { @@ -172,7 +181,7 @@ "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "Auto-invullen" + "message": "Automatisch invullen" }, "autoFillLogin": { "message": "Login automatisch invullen" @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Identiteit automatisch invullen" }, + "fillVerificationCode": { + "message": "Verificatiecode invullen" + }, + "fillVerificationCodeAria": { + "message": "Verificatiecode invullen", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Wachtwoord genereren (op klembord)" }, @@ -438,9 +454,6 @@ "length": { "message": "Lengte" }, - "passwordMinLength": { - "message": "Minimale wachtwoordlengte" - }, "uppercase": { "message": "Hoofdletters (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum aantal speciale tekens" }, - "avoidAmbChar": { - "message": "Dubbelzinnige tekens vermijden", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Dubbelzinnige tekens vermijden", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Deze extensie beoordelen" }, - "rateExtensionDesc": { - "message": "Je kunt ons helpen door een goede recensie achter te laten!" - }, "browserNotSupportClipboard": { "message": "Je webbrowser ondersteunt kopiëren naar plakbord niet. Kopieer handmatig." }, @@ -682,7 +688,7 @@ "message": "Nu vergrendelen" }, "lockAll": { - "message": "Vergrendel alles" + "message": "Alles vergrendelen" }, "immediately": { "message": "Onmiddellijk" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Identiteiten weergeven op de tabpagina voor gemakkelijk automatisch invullen." }, + "clickToAutofillOnVault": { + "message": "Klik op items om automatisch in te vullen op de kluisweergave" + }, "clearClipboard": { "message": "Klembord wissen", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1034,7 +1043,7 @@ "message": "Ja, nu bijwerken" }, "notificationUnlockDesc": { - "message": "Ontgrendel je Bitwarden-kluis om het auto-invulverzoek te voltooien." + "message": "Ontgrendel je Bitwarden-kluis om het automatisch invullen verzoek te voltooien." }, "notificationUnlock": { "message": "Ontgrendelen" @@ -1116,6 +1125,10 @@ "message": "WAARSCHUWING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Waarschuwing", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Kluisexport bevestigen" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Naar organisatie verplaatsen" }, - "share": { - "message": "Delen" - }, "movedItemToOrg": { "message": "$ITEMNAME$ verplaatst naar $ORGNAME$", "placeholders": { @@ -1190,7 +1200,7 @@ "message": "Geen bijlagen." }, "attachmentSaved": { - "message": "De bijlage is opgeslagen." + "message": "Bijlage opgeslagen" }, "file": { "message": "Bestand" @@ -1199,7 +1209,7 @@ "message": "Bestand om te delen" }, "selectFile": { - "message": "Selecteer een bestand." + "message": "Selecteer een bestand" }, "maxFileSize": { "message": "Maximale bestandsgrootte is 500 MB." @@ -1232,7 +1242,7 @@ "message": "1 GB versleutelde opslag voor bijlagen." }, "premiumSignUpEmergency": { - "message": "Noodtoegang" + "message": "Noodtoegang." }, "premiumSignUpTwoStepOptions": { "message": "Eigen opties voor tweestapsaanmelding zoals YubiKey en Duo." @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Voer de 6-cijferige verificatiecode uit je authenticatie-app in." }, + "authenticationTimeout": { + "message": "Authenticatie-timeout" + }, + "authenticationSessionTimedOut": { + "message": "De verificatiesessie is verlopen. Start het inlogproces opnieuw op." + }, "enterVerificationCodeEmail": { "message": "Voer de 6-cijferige verificatiecode in die via e-mail is verstuurd naar $EMAIL$.", "placeholders": { @@ -1409,10 +1425,10 @@ "message": "Specificeer de basis-URL van je zelfgehoste Bitwarden-installatie. Bijvoorbeeld: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Voor geavanceerde configuratie kun je de basis URL van elke service onafhankelijk opgeven." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, "customEnvironment": { "message": "Aangepaste omgeving" @@ -1443,14 +1459,14 @@ "message": "Pictogrammenserver-URL" }, "environmentSaved": { - "message": "De omgeving-URL's zijn opgeslagen." + "message": "Omgeving-URL's opgeslagen" }, "showAutoFillMenuOnFormFields": { - "message": "Auto-invulmenu op formuliervelden weergeven", + "message": "Automatisch invullen menu op formuliervelden weergeven", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Suggesties voor automatisch invullen" + "message": "Suggesties automatisch invullen" }, "showInlineMenuLabel": { "message": "Suggesties voor automatisch invullen op formuliervelden weergeven" @@ -1494,21 +1510,8 @@ "enableAutoFillOnPageLoadDesc": { "message": "Als een inlogformulier wordt gedetecteerd, dan worden de inloggegevens automatisch ingevuld." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Waarschuwing:$CLOSETAG$ Geconpromitteerde of niet-vertrouwde websites kunnen het automatische invullen bij het laden van de pagina misbruiken.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { - "message": "Gehackte of onbetrouwbare websites kunnen auto-invullen tijdens het laden van de pagina misbruiken." + "message": "Gehackte of onbetrouwbare websites kunnen automatisch invullen tijdens het laden van de pagina misbruiken." }, "learnMoreAboutAutofillOnPageLoadLinkText": { "message": "Meer over risico's lezen" @@ -1532,7 +1535,7 @@ "message": "Automatisch invullen bij laden van pagina" }, "autoFillOnPageLoadNo": { - "message": "Niet Automatisch invullen bij laden van pagina" + "message": "Niet automatisch invullen bij laden van pagina" }, "commandOpenPopup": { "message": "Open kluis in pop-up" @@ -1603,7 +1606,7 @@ "message": "Een herkenbare afbeelding naast iedere login weergeven." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Toon een herkenbare afbeelding naast elke login. Geldt voor alle ingelogde accounts." }, "enableBadgeCounter": { "message": "Teller weergeven" @@ -1687,7 +1690,7 @@ "message": "Dr." }, "mx": { - "message": "Mx" + "message": "Mx." }, "firstName": { "message": "Voornaam" @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identiteit" }, + "typeSshKey": { + "message": "SSH-sleutel" + }, "newItemHeader": { "message": "Nieuwe $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Veilige notities" }, + "sshKeys": { + "message": "SSH-sleutels" + }, "clear": { "message": "Wissen", "description": "To clear something out. example: To clear browser history." @@ -1984,10 +1993,10 @@ "message": "Ontgrendelen met PIN" }, "setYourPinTitle": { - "message": "PIN-code instellen" + "message": "PIN instellen" }, "setYourPinButton": { - "message": "PIN-code instellen" + "message": "PIN instellen" }, "setYourPinCode": { "message": "Stel je PIN-code in voor het ontgrendelen van Bitwarden. Je PIN-code wordt opnieuw ingesteld als je je ooit volledig afmeldt bij de applicatie." @@ -2031,9 +2040,6 @@ "clone": { "message": "Dupliceren" }, - "passwordGeneratorPolicyInEffect": { - "message": "Een of meer organisatiebeleidseisen heeft invloed op de instellingen van je generator." - }, "passwordGenerator": { "message": "Wachtwoordgenerator" }, @@ -2105,10 +2111,10 @@ "message": "Invullen en opslaan" }, "autoFillSuccessAndSavedUri": { - "message": "Automatisch gevuld item en opgeslagen URI" + "message": "Automatisch ingevuld item en opgeslagen URI" }, "autoFillSuccess": { - "message": "Automatisch gevuld item" + "message": "Item automatisch ingevuld " }, "insecurePageWarning": { "message": "Waarschuwing: Dit is een onbeveiligde HTTP-pagina waardoor anderen alle informatie die je verstuurt kunnen zien en wijzigen. Deze login is oorspronkelijk opgeslagen op een beveiligde (HTTPS) pagina." @@ -2219,7 +2225,7 @@ "message": "Fout bij vernieuwen toegangstoken" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Geen verversingstoken of API-sleutels gevonden. Probeer uit te loggen en weer in te loggen." }, "desktopSyncVerificationTitle": { "message": "Desktopsynchronisatieverificatie" @@ -2276,10 +2282,10 @@ "message": "Dit apparaat ondersteunt geen browserbiometrie." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Gebruiker vergrendeld of uitgelogd" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Ontgrendel deze gebruiker in de desktopapplicatie en probeer het opnieuw." }, "biometricsNotAvailableTitle": { "message": "Biometrisch ontgrendelen niet beschikbaar" @@ -2318,6 +2324,9 @@ "message": "Domeinen", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Geblokkeerde domeinen" + }, "excludedDomains": { "message": "Uitgesloten domeinen" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden zal voor deze domeinen niet vragen om de wachtwoorden op te slaan voor alle ingelogde accounts. Je moet de pagina verversen om de wijzigingen op te slaan." }, + "blockedDomainsDesc": { + "message": "Autofill en andere gerelateerde functies worden niet aangeboden voor deze websites. Vernieuw de pagina om de wijzigingen toe te passen." + }, + "autofillBlockedNotice": { + "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk of verander dit in de instellingen." + }, + "autofillBlockedTooltip": { + "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk in de instellingen." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Wijzigingen in geblokkeerde domeinen opgeslagen" + }, "excludedDomainsSavedSuccess": { "message": "Uitgesloten domeinwijzigingen opgeslagen" }, @@ -2373,14 +2394,6 @@ "message": "Send-details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Sends zoeken", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send toevoegen", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Tekst standaard verbergen" }, - "maxAccessCountReached": { - "message": "Maximum aantal keren benaderd", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Verlopen" }, - "pendingDeletion": { - "message": "Wordt verwijderd" - }, "passwordProtected": { "message": "Beveiligd met wachtwoord" }, @@ -2456,24 +2462,9 @@ "message": "Send bewerken", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Wat voor soort Send is dit?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Een vriendelijke naam om deze Send te beschrijven.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Het bestand dat je wilt versturen." - }, "deletionDate": { "message": "Verwijderingsdatum" }, - "deletionDateDesc": { - "message": "Deze Send wordt op de aangegeven datum en tijd definitief verwijderd.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Op deze datum wordt de Send definitief verwijderd.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Vervaldatum" }, - "expirationDateDesc": { - "message": "Als dit is ingesteld verloopt deze Send op een specifieke datum en tijd.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dag" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Aangepast" }, - "maximumAccessCount": { - "message": "Maximum toegangsaantal" - }, - "maximumAccessCountDesc": { - "message": "Als dit is ingesteld kunnen gebruikers deze Send niet meer benaderen zodra het maximale aantal toegang is bereikt.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Vereis optioneel een wachtwoord voor gebruikers om toegang te krijgen tot deze Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Voeg een optioneel wachtwoord toe voor ontvangers om toegang te krijgen tot deze Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Privénotities over deze Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Schakel deze Send uit zodat niemand hem kan benaderen.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopieer de link van deze Send bij het opslaan naar het klembord.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "De tekst die je wilt versturen." - }, - "sendHideText": { - "message": "De tekst van deze Send standaard verbergen.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Huidige toegangsaantal" - }, "createSend": { "message": "Nieuwe Send aanmaken", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Voor je begint" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Om een datumkiezer in kalenderstijl te gebruiken", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klik hier", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "om een pop-up te openen.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "De opgegeven vervaldatum is niet geldig." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Er is een fout opgetreden bij het opslaan van je verwijder- en vervaldatum." }, - "hideEmail": { - "message": "Verberg mijn e-mailadres voor ontvangers." - }, "hideYourEmail": { "message": "Je e-mailadres voor ontvangers verbergen." }, - "sendOptionsPolicyInEffect": { - "message": "Een of meer organisatiebeleidseisen heeft invloed op de mogelijkheden van je Send." - }, "passwordPrompt": { "message": "Hoofdwachtwoord opnieuw vragen" }, @@ -2702,11 +2638,11 @@ "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": "De machtigingen van je organisatie zijn bijgewerkt, waardoor je een hoofdwachtwoord moet instellen.", "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": "Je organisatie vereist dat je een hoofdwachtwoord instelt.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -2845,7 +2781,7 @@ "message": "Persoonlijke kluis exporteren" }, "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": "Alleen de individuele kluisitems die gekoppeld zijn aan $EMAIL$ worden geëxporteerd. Kluisitems van organisaties worden niet meegenomen. Alleen de informatie over het kluisitem wordt geëxporteerd en niet de bijbehorende bijlagen.", "placeholders": { "email": { "content": "$1", @@ -2868,8 +2804,19 @@ "error": { "message": "Fout" }, - "regenerateUsername": { - "message": "Gebruikersnaam opnieuw genereren" + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Gebruikersnamen genereren" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "E-mailadres genereren" }, - "generatorBoundariesHint": { - "message": "Waarde moet tussen $MIN$ en $MAX$ liggen", + "spinboxBoundariesHint": { + "message": "Waarde moet tussen $MIN$ en $MAX$ liggen.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Type gebruikersnaam" + "passwordLengthRecommendationHint": { + "message": " Gebruik $RECOMMENDED$ tekens of meer om een sterk wachtwoord te genereren.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Gebruik $RECOMMENDED$ woorden of meer om een sterke wachtwoordzin te genereren.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-mailadres-met-plus", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Websitenaam" }, - "whatWouldYouLikeToGenerate": { - "message": "Wat wil je genereren?" - }, - "passwordType": { - "message": "Type wachtwoord" - }, "service": { "message": "Dienst" }, @@ -2940,7 +2898,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ fout: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2958,7 +2916,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Website: $WEBSITE$. Gegenereerd door Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2968,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ongeldig $SERVICENAME$ API token", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2978,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ongeldige $SERVICENAME$ API token: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2992,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Kan $SERVICENAME$ gemaskeerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3002,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ongeldig $SERVICENAME$ domein.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3012,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Ongeldige $SERVICENAME$ url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3022,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Onbekende $SERVICENAME$ fout opgetreden.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3032,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Onbekende doorstuurder: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3088,7 +3046,7 @@ "message": "zelfgehost" }, "thirdParty": { - "message": "van derden" + "message": "Derde partij" }, "thirdPartyServerMessage": { "message": "Verbonden met server implementatie van derden, $SERVERNAME$. Reproduceer bugs via de officiële server, of meld ze bij het serverproject.", @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Melding opnieuw verzenden" }, - "viewAllLoginOptions": { - "message": "Alle loginopties bekijken" + "viewAllLogInOptions": { + "message": "Alle inlogopties bekijken" + }, + "viewAllLoginOptionsV1": { + "message": "Alle inlogopties weergeven" }, "notificationSentDevice": { "message": "Er is een melding naar je apparaat verzonden." }, + "aNotificationWasSentToYourDevice": { + "message": "Er is een melding naar je apparaat verzonden" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Zorg ervoor dat je kluis is ontgrendeld en de vingerafdrukzin hetzelfde is op het andere apparaat" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Je krijgt een melding zodra de aanvraag is goedgekeurd" + }, + "needAnotherOptionV1": { + "message": "Nog een optie nodig?" + }, "loginInitiated": { "message": "Inloggen gestart" }, @@ -3181,10 +3154,10 @@ "message": "Je organisatiebeleid heeft het automatisch invullen bij laden van pagina ingeschakeld." }, "howToAutofill": { - "message": "Hoe automatisch aanvullen" + "message": "Hoe automatisch invullen" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Selecteer een item in dit scherm, gebruik de sneltoets $COMMAND$ of verken andere opties in de instellingen.", "placeholders": { "command": { "content": "$1", @@ -3193,16 +3166,16 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Selecteer een item in dit scherm of verken andere opties in de instellingen." }, "gotIt": { - "message": "Ik snap het" + "message": "Oké" }, "autofillSettings": { "message": "Instellingen automatisch invullen" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Shortcut voor automatisch invullen" + "message": "Snelkoppeling automatisch invullen" }, "autofillKeyboardShortcutUpdateLabel": { "message": "Snelkoppeling wijzigen" @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opent in een nieuw venster" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Onthoud dit apparaat om in het vervolg naadloos in te loggen" + }, "deviceApprovalRequired": { "message": "Apparaattoestemming vereist. Kies een goedkeuringsoptie hieronder:" }, + "deviceApprovalRequiredV2": { + "message": "Toestemming apparaat vereist" + }, + "selectAnApprovalOptionBelow": { + "message": "Kies hieronder een goedkeuringsoptie" + }, "rememberThisDevice": { "message": "Dit apparaat onthouden" }, @@ -3290,7 +3272,7 @@ "message": "Algemeen" }, "display": { - "message": "Display" + "message": "Weergave" }, "accountSuccessfullyCreated": { "message": "Account succesvol aangemaakt!" @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Gebruikerse-mailadres ontbreekt" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mailadres voor actieve gebruiker niet gevonden. Je wordt uitgelogd." + }, "deviceTrusted": { "message": "Vertrouwd apparaat" }, @@ -3416,7 +3401,7 @@ "message": "-- Type om te filteren --" }, "multiSelectLoading": { - "message": "Opties ophalen..." + "message": "Opties ophalen…" }, "multiSelectNotFound": { "message": "Geen items gevonden" @@ -3457,7 +3442,7 @@ "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importeren...", + "message": "Importeren…", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { @@ -3476,33 +3461,33 @@ "message": "Aliasdomein" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be autofilled on page load. Autofill on page load turned off.", + "message": "Items met hoofdwachtwoord re-prompt kunnen niet automatisch worden ingevuld bij het laden van de pagina. Automatisch invullen bij laden van pagina uitgeschakeld.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "Automatisch invullen bij het laden van een pagina ingesteld op de standaardinstelling.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "Hoofdwachtwoord herhaalprompt uitschakelen om dit veld te bewerken", "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { "message": "Zijnavigatie schakelen" }, "skipToContent": { - "message": "Skip to content" + "message": "Overslaan naar inhoud" }, "bitwardenOverlayButton": { "message": "Menuknop Bitwarden automatisch invullen", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Bitwarden auto-invulmenu in- en uitschakelen", + "message": "Bitwarden automatisch invullen menu in- en uitschakelen", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden auto-invulmenu", + "message": "Bitwarden automatisch invullen menu", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3521,8 +3506,16 @@ "message": "Je account ontgrendelen, opent in een nieuw venster", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-gebaseerde eenmalige wachtwoord verificatiecode", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Resterende tijd voordat de huidige TOTP vervalt", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { - "message": "Inloggegevens invullen voor", + "message": "Referenties invullen voor", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { @@ -3566,7 +3559,7 @@ "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden auto-invulmenu beschikbaar. Druk op de pijltjestoets omlaag om te selecteren.", + "message": "Bitwarden automatisch invullen menu beschikbaar. Druk op de pijltjestoets omlaag om te selecteren.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -3610,7 +3603,7 @@ "message": "Deze actie vereist verificatie actie. Stel een pincode in om door te gaan." }, "setPin": { - "message": "Pincode instellen" + "message": "PIN instellen" }, "verifyWithBiometrics": { "message": "Biometrisch ontgrendelen" @@ -3655,7 +3648,7 @@ "message": "Fout bij het verbinden met de Duo-service. Gebruik een andere tweestapsaanmeldingsmethode of neem contact op met Duo voor hulp." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Start Duo en volg de stappen om het inloggen te voltooien." }, "duoRequiredForAccount": { "message": "Jouw account vereist Duo-tweestapsaanmelding." @@ -3664,10 +3657,10 @@ "message": "Open de extensie om in te loggen." }, "popoutExtension": { - "message": "Popout extension" + "message": "Pop-out extensie" }, "launchDuo": { - "message": "Launch Duo" + "message": "Duo starten" }, "importFormatError": { "message": "De gegevens zijn niet correct opgemaakt. Controleer je importbestand en probeer het opnieuw." @@ -3749,6 +3742,9 @@ "accessing": { "message": "Toegang verkrijgen" }, + "loggedInExclamation": { + "message": "Ingelogd!" + }, "passkeyNotCopied": { "message": "Passkey wordt niet gekopieerd" }, @@ -3792,7 +3788,7 @@ "message": "Kies een passkey om mee in te loggen" }, "passkeyItem": { - "message": "Passkey-Item" + "message": "Passkey item" }, "overwritePasskey": { "message": "Passkey overschrijven?" @@ -3834,7 +3830,7 @@ "message": "LastPass Email" }, "importingYourAccount": { - "message": "Account impoteren..." + "message": "Account impoteren…" }, "lastPassMFARequired": { "message": "LastPass multifactor-authenticatie vereist" @@ -3858,10 +3854,10 @@ "message": "Wacht op SSO-authenticatie" }, "awaitingSSODesc": { - "message": "Ga door met inloggen met de inloggegevens van je bedrijf." + "message": "Ga door met inloggen met de referenties van je bedrijf." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "Zie gedetailleerde instructies op onze helpsite op", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { @@ -3877,52 +3873,52 @@ "message": "Collectie" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Steek de YubiKey die bij je LastPass account hoort in de USB poort van je computer en druk dan op de knop." }, "switchAccount": { - "message": "Switch account" + "message": "Account wisselen" }, "switchAccounts": { - "message": "Switch accounts" + "message": "Accounts wisselen" }, "switchToAccount": { - "message": "Switch to account" + "message": "Wisselen naar account" }, "activeAccount": { - "message": "Active account" + "message": "Actief account" }, "availableAccounts": { "message": "Beschikbare accounts" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "Accountlimiet bereikt. Log uit bij een account om een andere toe te voegen." }, "active": { - "message": "active" + "message": "actief" }, "locked": { - "message": "locked" + "message": "vergrendeld" }, "unlocked": { - "message": "unlocked" + "message": "ontgrendeld" }, "server": { "message": "server" }, "hostedAt": { - "message": "hosted at" + "message": "gehost op" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Gebruik je apparaat of hardwaresleutel" }, "justOnce": { - "message": "Just once" + "message": "Eenmalig" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "Altijd voor deze site" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ toegevoegd aan uitgesloten domeinen.", "placeholders": { "domain": { "content": "$1", @@ -3983,7 +3979,7 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Referenties succesvol opgeslagen!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { @@ -3991,7 +3987,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Referenties succesvol bijgewerkt!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { @@ -3999,7 +3995,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Fout bij het opslaan van referenties. Controleer console voor details.", "description": "Notification message for when saving credentials has failed." }, "success": { @@ -4012,7 +4008,7 @@ "message": "Passkey verwijderd" }, "autofillSuggestions": { - "message": "Suggesties voor automatisch invullen" + "message": "Suggesties automatisch invullen" }, "autofillSuggestionsTip": { "message": "Inlogitem opslaan voor automatisch invullen op deze site" @@ -4027,7 +4023,7 @@ "message": "Wis filters of probeer een andere zoekterm" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "Info kopiëren - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4037,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "Notitie kopiëren - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4047,7 +4043,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Meer opties, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4057,7 +4053,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Meer opties - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Item bekijken - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4093,22 +4089,22 @@ "message": "Aan collecties toewijzen" }, "copyEmail": { - "message": "Copy email" + "message": "E-mail kopiëren" }, "copyPhone": { - "message": "Copy phone" + "message": "Telefoon kopiëren" }, "copyAddress": { - "message": "Copy address" + "message": "Adres kopiëren" }, "adminConsole": { - "message": "Admin Console" + "message": "Beheerconsole" }, "accountSecurity": { "message": "Accountbeveiliging" }, "notifications": { - "message": "Notifications" + "message": "Meldingen" }, "appearance": { "message": "Uiterlijk" @@ -4140,7 +4136,7 @@ } }, "new": { - "message": "New" + "message": "Nieuw" }, "removeItem": { "message": "Verwijder $NAME$", @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter kluis" + }, + "filterApplied": { + "message": "Eén filter toegepast" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters toegepast", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Persoonlijke gegevens" }, @@ -4263,7 +4274,7 @@ "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Inloggegevens" + "message": "Login referenties" }, "authenticatorKey": { "message": "Authenticatiesleutel" @@ -4564,13 +4575,13 @@ "message": "Itemlocatie" }, "fileSend": { - "message": "Bestand-Sends" + "message": "Bestand verzenden" }, "fileSends": { - "message": "Bestand-Sends" + "message": "Bestanden verzenden" }, "textSend": { - "message": "Text Send" + "message": "Tekst-Sends" }, "textSends": { "message": "Tekst-Sends" @@ -4585,7 +4596,10 @@ "message": "Accountacties" }, "showNumberOfAutofillSuggestions": { - "message": "Aantal login-autofill-suggesties op het extensie-pictogram weergeven" + "message": "Aantal login automatisch invullen suggesties op het extensie-pictogram weergeven" + }, + "showQuickCopyActions": { + "message": "Toon snelle kopieeracties in de kluis" }, "systemDefault": { "message": "Systeemstandaard" @@ -4593,6 +4607,30 @@ "enterprisePolicyRequirementsApplied": { "message": "Bedrijfsbeleidseisen zijn op deze instelling toegepast" }, + "sshPrivateKey": { + "message": "Privésleutel" + }, + "sshPublicKey": { + "message": "Publieke sleutel" + }, + "sshFingerprint": { + "message": "Vingerafdruk" + }, + "sshKeyAlgorithm": { + "message": "Sleuteltype" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bit" + }, "retry": { "message": "Opnieuw proberen" }, @@ -4632,23 +4670,50 @@ "noEditPermissions": { "message": "Je hebt geen toestemming om dit item te bewerken" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat de Bitwarden-desktopapp is afgesloten." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authenticating": { "message": "Aan het inloggen" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Gegenereerd wachtwoordin vullen", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Wachtwoord opnieuw gegenereerd", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Login opslaan in Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Spatie", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { @@ -4660,51 +4725,51 @@ "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Uitroepteken", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Apenstaartje", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Hekje", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Dollarteken", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Procentteken", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Dakje", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "En-teken", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Sterretje", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Haakje opnenen", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Haakje sluiten", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Liggend streepje", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Koppelteken", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { @@ -4712,80 +4777,131 @@ "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Gelijkteken", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Accolade openen", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Accolade sluiten", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Blokhaak openen", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Blokhaak sluiten", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Verticale streep", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Backslash", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Dubbele punt", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Puntkomma", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Dubbel aanhalingsteken", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Enkel aanhalingsteken", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Kleiner dan", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Groter dan", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Komma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Punt", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Vraagteken", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Slash", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Kleine letters" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Hoofdletters" }, "generatedPassword": { - "message": "Generated password" + "message": "Gegenereerd wachtwoord" + }, + "compactMode": { + "message": "Compacte modus" + }, + "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" + }, + "wide": { + "message": "Breed" + }, + "extraWide": { + "message": "Extra breed" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index b8075804229..e34751eea7d 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index b8075804229..e34751eea7d 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index c3c48b34e8a..4b7d3a19fc4 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -20,7 +20,7 @@ "message": "Utwórz konto" }, "newToBitwarden": { - "message": "Nowy użytkownik Bitwarden?" + "message": "New to Bitwarden?" }, "logInWithPasskey": { "message": "Zaloguj się używając passkey" @@ -35,7 +35,7 @@ "message": "Ustaw silne hasło" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Ukończ tworzenie konta poprzez utworzenie hasła" + "message": "Ukończ tworzenie konta poprzez ustawienie hasła" }, "enterpriseSingleSignOn": { "message": "Logowanie jednokrotne" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopiuj numer licencji" }, + "copyPrivateKey": { + "message": "Skopiuj klucz prywatny" + }, + "copyPublicKey": { + "message": "Skopiuj klucz publiczny" + }, + "copyFingerprint": { + "message": "Skopiuj odcisk palca" + }, "copyCustomField": { "message": "Kopiuj $FIELD$", "placeholders": { @@ -168,7 +177,7 @@ "message": "Kopiuj notatki" }, "fill": { - "message": "Uzupełnij", + "message": "Wypełnij", "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autouzupełnianie tożsamości" }, + "fillVerificationCode": { + "message": "Wypełnij kod weryfikacyjny" + }, + "fillVerificationCodeAria": { + "message": "Wypełnij kod weryfikacyjny", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Wygeneruj hasło (do schowka)" }, @@ -438,9 +454,6 @@ "length": { "message": "Długość" }, - "passwordMinLength": { - "message": "Minimalna długość hasła" - }, "uppercase": { "message": "Wielkie litery (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimalna liczba znaków specjalnych" }, - "avoidAmbChar": { - "message": "Unikaj niejednoznacznych znaków", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Unikaj niejednoznacznych znaków", "description": "Label for the avoid ambiguous characters checkbox." @@ -612,7 +621,7 @@ "message": "Inne" }, "unlockMethods": { - "message": "Odblokuj Opcje" + "message": "Opcje odblokowania" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Ustaw metodę odblokowania, aby zmienić czas blokowania sejfu." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Oceń rozszerzenie" }, - "rateExtensionDesc": { - "message": "Wesprzyj nas pozytywną opinią!" - }, "browserNotSupportClipboard": { "message": "Przeglądarka nie obsługuje łatwego kopiowania schowka. Skopiuj element ręcznie." }, @@ -972,7 +978,7 @@ "message": "Poproś o dodanie danych logowania" }, "vaultSaveOptionsTitle": { - "message": "Zapisz do ustawień sejfu" + "message": "Opcje zapisywania w sejfie" }, "addLoginNotificationDesc": { "message": "\"Dodaj powiadomienia logowania\" automatycznie wyświetla monit o zapisanie nowych danych logowania do sejfu przy każdym pierwszym logowaniu." @@ -990,7 +996,7 @@ "message": "Pokaż elementy karty na stronie głównej, aby ułatwić autouzupełnianie." }, "showIdentitiesInVaultView": { - "message": "Pokaż tożsamośći jako sugestie autouzupełniania w widoku sejfu" + "message": "Pokaż tożsamości jako sugestie autouzupełniania w widoku sejfu" }, "showIdentitiesCurrentTab": { "message": "Pokaż tożsamości na stronie głównej" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Pokaż elementy tożsamości na stronie głównej, aby ułatwić autouzupełnianie." }, + "clickToAutofillOnVault": { + "message": "Kliknij na dane logowania, aby autouzupełnić w widoku Sejfu" + }, "clearClipboard": { "message": "Wyczyść schowek", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "UWAGA", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Ostrzeżenie", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Potwierdź eksportowanie sejfu" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Przenieś do organizacji" }, - "share": { - "message": "Udostępnij" - }, "movedItemToOrg": { "message": "Element $ITEMNAME$ został przeniesiony do organizacji $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Wpisz 6-cyfrowy kod weryfikacyjny z aplikacji uwierzytelniającej." }, + "authenticationTimeout": { + "message": "Limit czasu uwierzytelniania" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Wpisz 6-cyfrowy kod weryfikacyjny, który został przesłany na adres $EMAIL$.", "placeholders": { @@ -1468,7 +1484,7 @@ "message": "Dotyczy wszystkich zalogowanych kont." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Wyłącz wbudowany w przeglądarkę menedżera haseł, aby uniknąć konfliktów." + "message": "Wyłącz wbudowany w przeglądarkę menedżer haseł, aby uniknąć konfliktów." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Edytuj ustawienia przeglądarki." @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Jeśli zostanie wykryty formularz logowania, automatycznie uzupełnij dane logowania po załadowaniu strony." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Ostrzeżenie:$CLOSETAG$ Niebezpieczne witryny są w stanie wykorzystać autouzupełnianie przy wczytywaniu strony.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Zaatakowane lub niezaufane witryny internetowe mogą wykorzystać funkcję autouzupełniania podczas wczytywania strony, aby wyrządzić szkody." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Tożsamość" }, + "typeSshKey": { + "message": "Klucz SSH" + }, "newItemHeader": { "message": "Nowy $TYPE$", "placeholders": { @@ -1801,7 +1807,7 @@ "message": "Wyczyść historię generatora" }, "cleargGeneratorHistoryDescription": { - "message": "Jeśli kontynuujesz, wszystkie elementy zostaną usunięte z historii generatora. Czy chcesz kontynuować mimo to?" + "message": "Jeśli zatwierdzisz, wszystkie wygenerowane hasła zostaną usunięte z historii generatora. Czy chcesz kontynuować mimo to?" }, "back": { "message": "Powrót" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Bezpieczne notatki" }, + "sshKeys": { + "message": "Klucze SSH" + }, "clear": { "message": "Wyczyść", "description": "To clear something out. example: To clear browser history." @@ -1920,7 +1929,7 @@ "message": "Wyczyść historię" }, "nothingToShow": { - "message": "Nic do pokazania" + "message": "Brak zawartości do pokazania" }, "nothingGeneratedRecently": { "message": "Nic nie zostało wygenerowane przez ciebie w ostatnim czasie" @@ -2031,9 +2040,6 @@ "clone": { "message": "Klonuj" }, - "passwordGeneratorPolicyInEffect": { - "message": "Co najmniej jedna zasada organizacji wpływa na ustawienia generatora." - }, "passwordGenerator": { "message": "Generator hasła" }, @@ -2318,6 +2324,9 @@ "message": "Domeny", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Wykluczone domeny" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Aplikacja Bitwarden nie będzie proponować zapisywania danych logowania dla tych domen dla wszystkich zalogowanych kont. Musisz odświeżyć stronę, aby zastosowywać zmiany." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Strona internetowa $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Zmiany w wykluczonych domenach zapisane" }, @@ -2373,14 +2394,6 @@ "message": "Szczegóły Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Szukaj w wysyłkach", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Dodaj wysyłkę", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Tekst" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Domyślnie ukryj tekst" }, - "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." - }, "expired": { "message": "Wygasła" }, - "pendingDeletion": { - "message": "Oczekiwanie na usunięcie" - }, "passwordProtected": { "message": "Chroniona hasłem" }, @@ -2456,24 +2462,9 @@ "message": "Edytuj wysyłkę", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Jakiego typu jest to wysyłka?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Nazwa wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Plik, który chcesz wysłać." - }, "deletionDate": { "message": "Data usunięcia" }, - "deletionDateDesc": { - "message": "Wysyłka zostanie trwale usunięta w określonym czasie.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send zostanie trwale usunięte w tej dacie.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Data wygaśnięcia" }, - "expirationDateDesc": { - "message": "Jeśli funkcja jest włączona, dostęp do wysyłki wygaśnie po określonym czasie.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dzień" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Niestandardowe" }, - "maximumAccessCount": { - "message": "Maksymalna liczba dostępów" - }, - "maximumAccessCountDesc": { - "message": "Jeśli funkcja jest włączona, po osiągnięciu maksymalnej liczby dostępów, użytkownicy nie będą mieli dostępu do tej wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Opcjonalne hasło dla użytkownika, aby uzyskać dostęp do wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Zabezpiecz tę wiadomość hasłem, które będzie wymagane, aby uzyskać do niej dostęp.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Prywatne notatki o tej wysyłce.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Wyłącz wysyłkę, aby nikt nie miał do niej dostępu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Po zapisaniu wysyłki, skopiuj link do schowka.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Tekst, który chcesz wysłać." - }, - "sendHideText": { - "message": "Ukryj domyślnie tekst wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Obecna liczba dostępów" - }, "createSend": { "message": "Nowa wysyłka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2597,7 +2551,7 @@ "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": "Otworzyć rozszerzenie w nowym oknie?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Zanim zaczniesz" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Aby wybrać datę, ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "kliknij tutaj", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "w celu otwarcia rozszerzenia w oknie.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Data wygaśnięcia nie jest prawidłowa." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Wystąpił błąd podczas zapisywania dat usunięcia i wygaśnięcia." }, - "hideEmail": { - "message": "Ukryj mój adres e-mail przed odbiorcami." - }, "hideYourEmail": { "message": "Ukryj mój adres e-mail przed oglądającymi." }, - "sendOptionsPolicyInEffect": { - "message": "Co najmniej jedna zasada organizacji wpływa na ustawienia wysyłek." - }, "passwordPrompt": { "message": "Potwierdź hasłem głównym" }, @@ -2868,17 +2804,28 @@ "error": { "message": "Błąd" }, - "regenerateUsername": { - "message": "Wygeneruj ponownie nazwę użytkownika" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Wygeneruj nazwę użytkownika" }, "generateEmail": { - "message": "Wygenruj adres e-mail" + "message": "Wygeneruj e-mail" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Wartość musi być pomiędzy $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Rodzaj nazwy użytkownika" + "passwordLengthRecommendationHint": { + "message": " Użyj $RECOMMENDED$ znaków lub więcej, aby wygenerować silne hasło.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Użyj $RECOMMENDED$ słów lub więcej, aby wygenerować silne hasło.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Adres e-mail z plusem", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nazwa strony" }, - "whatWouldYouLikeToGenerate": { - "message": "Co chcesz wygenerować?" - }, - "passwordType": { - "message": "Rodzaj hasła" - }, "service": { "message": "Usługa" }, @@ -2936,7 +2894,7 @@ "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Wybierz domenę, która jest obsługiwana przez wybraną usługę", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Wyślij ponownie powiadomienie" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Zobacz wszystkie sposoby logowania" + }, + "viewAllLoginOptionsV1": { "message": "Zobacz wszystkie sposoby logowania" }, "notificationSentDevice": { "message": "Powiadomienie zostało wysłane na urządzenie." }, + "aNotificationWasSentToYourDevice": { + "message": "Powiadomienie zostało wysłane na twoje urządzenie" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Potrzebujesz innego sposobu?" + }, "loginInitiated": { "message": "Logowanie rozpoczęte" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Otwiera w nowym oknie" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamiętaj to urządzenie, aby przyszłe logowania były bezproblemowe" + }, "deviceApprovalRequired": { "message": "Wymagane zatwierdzenie urządzenia. Wybierz opcję zatwierdzenia poniżej:" }, + "deviceApprovalRequiredV2": { + "message": "Wymagane zatwierdzenie urządzenia" + }, + "selectAnApprovalOptionBelow": { + "message": "Wybierz opcję zatwierdzenia poniżej" + }, "rememberThisDevice": { "message": "Zapamiętaj to urządzenie" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Brak adresu e-mail użytkownika" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Nie znaleziono aktywnego adresu e-mail. Trwa wylogowanie." + }, "deviceTrusted": { "message": "Zaufano urządzeniu" }, @@ -3521,6 +3506,14 @@ "message": "Odblokuj swoje konto, otwiera się w nowym oknie", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Pozostały czas do wygaśnięcia bieżącego TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Wypełnij dane logowania dla", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Uzyskiwanie dostępu" }, + "loggedInExclamation": { + "message": "Zalogowano!" + }, "passkeyNotCopied": { "message": "Passkey nie zostanie skopiowany" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtry" }, + "filterVault": { + "message": "Filtruj sejf" + }, + "filterApplied": { + "message": "Zastosowano jeden filtr" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtrów zastosowanych", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Dane osobowe" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Pokaż liczbę sugestii autouzupełniania logowania na ikonie rozszerzenia" }, + "showQuickCopyActions": { + "message": "Pokaż akcje szybkiego kopiowania w Sejfie" + }, "systemDefault": { "message": "Domyślny systemu" }, "enterprisePolicyRequirementsApplied": { "message": "Do tego ustalenia zastosowano wymogi polityki przedsiębiorstw" }, + "sshPrivateKey": { + "message": "Klucz prywatny" + }, + "sshPublicKey": { + "message": "Klucz publiczny" + }, + "sshFingerprint": { + "message": "Odcisk palca" + }, + "sshKeyAlgorithm": { + "message": "Typ klucza" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bitowy" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bitowy" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bitowy" + }, "retry": { "message": "Powtórz" }, @@ -4632,11 +4670,38 @@ "noEditPermissions": { "message": "Nie masz uprawnień do edycji tego elementu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Uwierzytelnianie" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Uzupełnij wygenerowanym hasłem", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { @@ -4652,7 +4717,7 @@ "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Tylda", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { @@ -4692,19 +4757,19 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Prawy nawias okrągły", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Prawy nawias okrągły", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Podkreślenie", + "message": "Znak podkreślenia", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Myślnik", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { @@ -4732,7 +4797,7 @@ "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Pionowa kreska", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { @@ -4740,39 +4805,39 @@ "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Dwukropek", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Średnik", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Cudzysłów", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrof", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Mniejszy niż", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Większy niż", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Przecinek", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Kropka", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Znak zapytania", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { @@ -4780,12 +4845,63 @@ "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Małe litery" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Wielkie litery" }, "generatedPassword": { - "message": "Generated password" + "message": "Wygenerowane hasło" + }, + "compactMode": { + "message": "Tryb kompaktowy" + }, + "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": "Szerokość rozszerzenia" + }, + "wide": { + "message": "Szerokie" + }, + "extraWide": { + "message": "Bardzo szerokie" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 047ca09e7a4..e3409b1da52 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Gerenciador de Senhas", + "message": "Gerenciador de senhas Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -20,16 +20,16 @@ "message": "Criar Conta" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novo no Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Iniciar sessão com a chave de acesso" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usar login único" }, "welcomeBack": { - "message": "Welcome back" + "message": "Bem vindo de volta" }, "setAStrongPassword": { "message": "Defina uma senha forte" @@ -84,7 +84,7 @@ "message": "Juntar-se à organização" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Entrar em $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +120,7 @@ "message": "Copiar Senha" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copiar senha" }, "copyNote": { "message": "Copiar Nota" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copiar número da CNH" }, + "copyPrivateKey": { + "message": "Copiar chave privada" + }, + "copyPublicKey": { + "message": "Copiar chave pública" + }, + "copyFingerprint": { + "message": "Copiar impressão digital" + }, "copyCustomField": { "message": "Copiar $FIELD$", "placeholders": { @@ -168,7 +177,7 @@ "message": "Copiar Notas" }, "fill": { - "message": "Fill", + "message": "Preencher", "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Preenchimento automático identidade" }, + "fillVerificationCode": { + "message": "Preencher o código de verificação" + }, + "fillVerificationCodeAria": { + "message": "Preencher o código de verificação", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Gerar Senha (copiada)" }, @@ -226,7 +242,7 @@ "message": "Correio eletrônico da conta" }, "requestHint": { - "message": "Pedir dica" + "message": "Solicitar dica" }, "requestPasswordHint": { "message": "Dica da senha mestra" @@ -280,7 +296,7 @@ "message": "Continuar na extensão da loja do navegador?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Ajude outros a descobrir se o Bitwarden está certo para eles. Visite a loja de extensões do seu navegador e deixe uma classificação agora." + "message": "Ajude outras pessoas a descobrirem se o Bitwarden é o que elas estão procurando. Visite a loja de extensões do seu navegador e deixe uma classificação agora." }, "changeMasterPasswordOnWebConfirmation": { "message": "Você pode alterar a sua senha mestra no aplicativo web Bitwarden." @@ -324,7 +340,7 @@ "message": "Gerenciador de Segredos Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Armazene, gerencie e compartilhe segredos de desenvolvedor com o Gerenciador de segredos do Bitwarden. Saiba mais no site bitwarden.com." + "message": "Armazene, gerencie e compartilhe senhas de desenvolvedor com o Gerenciador de segredos do Bitwarden. Saiba mais no site bitwarden.com." }, "passwordlessDotDev": { "message": "Passwordless.dev" @@ -333,10 +349,10 @@ "message": "Crie experiências de login suaves e seguras, livres de senhas tradicionais com Passwordless.dev. Saiba mais no site bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Famílias do Bitwarden Grátis" + "message": "Plano Familiar do Bitwarden Grátis" }, "freeBitwardenFamiliesPageDesc": { - "message": "Você é elegível para as Famílias do Bitwarden Grátis. Resgate esta oferta hoje no aplicativo web." + "message": "Você é elegível para o plano Familiar do Bitwarden Grátis. Resgate esta oferta hoje no aplicativo web." }, "version": { "message": "Versão" @@ -427,7 +443,7 @@ "message": "Gerar Senha" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Gerar frase secreta" }, "regeneratePassword": { "message": "Gerar Nova Senha" @@ -438,9 +454,6 @@ "length": { "message": "Comprimento" }, - "passwordMinLength": { - "message": "Tamanho mínimo da senha" - }, "uppercase": { "message": "Maiúsculas (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,12 +525,8 @@ "minSpecial": { "message": "Especiais Mínimos" }, - "avoidAmbChar": { - "message": "Evitar Caracteres Ambíguos", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { - "message": "Evitar Caracteres Ambíguos", + "message": "Evitar caracteres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -591,7 +600,7 @@ "message": "Abrir site" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Iniciar site $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "Avaliar a Extensão" }, - "rateExtensionDesc": { - "message": "Por favor considere ajudar-nos com uma boa avaliação!" - }, "browserNotSupportClipboard": { "message": "O seu navegador web não suporta cópia para a área de transferência. Em alternativa, copie manualmente." }, @@ -645,13 +651,13 @@ "message": "Seu cofre está trancado. Verifique sua identidade para continuar." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Seu cofre está bloqueado" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Sua conta está bloqueada" }, "or": { - "message": "or" + "message": "ou" }, "unlock": { "message": "Desbloquear" @@ -846,7 +852,7 @@ "message": "Fazer login" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Inicie a sessão no Bitwarden" }, "restartRegistration": { "message": "Reiniciar registro" @@ -885,7 +891,7 @@ "message": "Torne sua conta mais segura configurando o 'login' em duas etapas no aplicativo ‘web’ do Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continuar para o aplicativo da ‘web’?" + "message": "Continuar para o aplicativo web?" }, "editedFolder": { "message": "Pasta Editada" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Liste os itens de identidade na aba atual para facilitar preenchimento automático." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Limpar Área de Transferência", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "AVISO", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Atenção", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirmar Exportação do Cofre" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Mover para a Organização" }, - "share": { - "message": "Compartilhar" - }, "movedItemToOrg": { "message": "$ITEMNAME$ movido para $ORGNAME$", "placeholders": { @@ -1208,7 +1218,7 @@ "message": "Funcionalidade Indisponível" }, "encryptionKeyMigrationRequired": { - "message": "Migração de chave de criptografia necessária. Faça login através do cofre web para atualizar sua chave de criptografia." + "message": "É necessário migrar sua chave de criptografia. Faça login através do cofre web para atualizar sua chave de criptografia." }, "premiumMembership": { "message": "Assinatura Premium" @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Insira o código de verificação de 6 dígitos do seu aplicativo de autenticação." }, + "authenticationTimeout": { + "message": "Tempo de autenticação esgotado" + }, + "authenticationSessionTimedOut": { + "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de login." + }, "enterVerificationCodeEmail": { "message": "Insira o código de verificação de 6 dígitos que foi enviado por e-mail para $EMAIL$.", "placeholders": { @@ -1424,7 +1440,7 @@ "message": "URL do Servidor" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL do servidor auto-host", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1456,10 +1472,10 @@ "message": "Mostrar sugestões de preenchimento automático nos campos de formulários" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Exibir identidades como sugestões" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Exibir cards como sugestões" }, "showInlineMenuOnIconSelectionLabel": { "message": "Exibir sugestões quando o ícone for selecionado" @@ -1468,7 +1484,7 @@ "message": "Aplica-se a todas as contas conectadas." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Desative as configurações do gerenciador de senhas do seu navegador para evitar conflitos." + "message": "Desative o gerenciador de senhas padrão do seu navegador para evitar conflitos." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Editar configurações do navegador." @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Se um formulário de login for detectado, realizar automaticamente um auto-preenchimento quando a página web carregar." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Aviso:$CLOSETAG$ Comprometido ou sites não confiáveis podem explorar o autopreenchimento ao carregar a página.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Sites comprometidos ou não confiáveis podem tomar vantagem do autopreenchimento ao carregar a página." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identidade" }, + "typeSshKey": { + "message": "Chave SSH" + }, "newItemHeader": { "message": "Nova $TYPE$", "placeholders": { @@ -1795,13 +1801,13 @@ "message": "Histórico de Senha" }, "generatorHistory": { - "message": "Generator history" + "message": "Histórico do gerador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Limpar histórico do gerador" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Se continuar, todas as entradas serão permanentemente excluídas do histórico do gerador. Tem certeza que deseja continuar?" }, "back": { "message": "Voltar" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Notas seguras" }, + "sshKeys": { + "message": "Chaves SSH" + }, "clear": { "message": "Limpar", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "Limpar histórico" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Nada para mostrar" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Você não gerou nada recentemente" }, "remove": { "message": "Remover" @@ -2008,7 +2017,7 @@ "message": "Desbloquear com a biometria" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloquear com a senha mestra" }, "awaitDesktop": { "message": "Aguardando confirmação do desktop" @@ -2031,9 +2040,6 @@ "clone": { "message": "Clonar" }, - "passwordGeneratorPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão afetando as suas configurações do gerador." - }, "passwordGenerator": { "message": "Gerador de Senha" }, @@ -2183,7 +2189,7 @@ "message": "A sua nova senha mestra não cumpre aos requisitos da política." }, "receiveMarketingEmailsV2": { - "message": "Obtenha conselhos, novidades, e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." + "message": "Obtenha dicas, novidades e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." }, "unsubscribe": { "message": "Cancelar subscrição" @@ -2318,6 +2324,9 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domínios Excluídos" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "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": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Mudanças de domínios excluídos salvas" }, @@ -2373,14 +2394,6 @@ "message": "Enviar detalhes", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Pesquisar Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Adicionar Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Texto" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Ocultar texto por padrão" }, - "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." - }, "expired": { "message": "Expirado" }, - "pendingDeletion": { - "message": "Exclusão pendente" - }, "passwordProtected": { "message": "Protegido por senha" }, @@ -2456,24 +2462,9 @@ "message": "Editar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Que tipo de Send é este?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Um nome amigável para descrever este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "O arquivo que você deseja enviar." - }, "deletionDate": { "message": "Data de Exclusão" }, - "deletionDateDesc": { - "message": "O Send será eliminado permanentemente na data e hora especificadas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "O envio será eliminado permanentemente na data e hora especificadas.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Data de Validade" }, - "expirationDateDesc": { - "message": "Se definido, o acesso a este Send expirará na data e hora especificadas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dia" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalizado" }, - "maximumAccessCount": { - "message": "Contagem Máxima de Acessos" - }, - "maximumAccessCountDesc": { - "message": "Se atribuído, usuários não poderão mais acessar este Send assim que o número máximo de acessos for atingido.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Exigir opcionalmente uma senha para os usuários acessarem este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "Adicione uma senha opcional para os destinatários para acessar este Envio.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Notas privadas sobre esse Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Desative este Send para que ninguém possa acessá-lo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copiar o link deste Send para área de transferência após salvar.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "O texto que você deseja enviar." - }, - "sendHideText": { - "message": "Ocultar o texto deste Send por padrão.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Contagem Atual de Acessos" - }, "createSend": { "message": "Criar Novo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2561,11 +2515,11 @@ "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": "O envio estará disponível para qualquer pessoa com o link para a próxima 1 hora.", "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": "O envio estará disponível para qualquer pessoa com o link para as próximas $HOURS$ horas.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2575,11 +2529,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "O envio estará disponível para qualquer pessoa com o link para o próximo 1 dia.", "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": "O envio estará disponível para qualquer pessoa com o link para os próximos $DAYS$ dias.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Antes de começar" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Para usar um seletor de data no estilo calendário", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "clique aqui", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "para abrir a sua janela.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "A data de validade fornecida não é válida." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Ocorreu um erro ao salvar as suas datas de exclusão e validade." }, - "hideEmail": { - "message": "Ocultar meu endereço de e-mail dos destinatários." - }, "hideYourEmail": { "message": "Ocultar meu endereço de correio eletrônico dos destinatários." }, - "sendOptionsPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão afetando as suas opções de Send." - }, "passwordPrompt": { "message": "Solicitação nova de senha mestra" }, @@ -2710,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "fora do $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2868,17 +2804,28 @@ "error": { "message": "Erro" }, - "regenerateUsername": { - "message": "Recriar Usuário" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Gerar Usuário" }, "generateEmail": { - "message": "Generate email" + "message": "Gerar e-mail" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Valor deve ser entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tipo de usuário" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ caracteres ou mais para gerar uma senha forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use palavras $RECOMMENDED$ ou mais para gerar uma frase secreta 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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-mail alternativo (com um +)", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nome do Site" }, - "whatWouldYouLikeToGenerate": { - "message": "O que você gostaria de gerar?" - }, - "passwordType": { - "message": "Categoria de Senha" - }, "service": { "message": "Serviço" }, @@ -2932,11 +2890,11 @@ "message": "Gere um apelido de e-mail com um serviço de encaminhamento externo." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domínio de e-mail", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Escolha um domínio que seja suportado pelo serviço selecionado", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Reenviar notificação" }, - "viewAllLoginOptions": { - "message": "Ver todas as opções de login" + "viewAllLogInOptions": { + "message": "Visualizar todas as opções de login" + }, + "viewAllLoginOptionsV1": { + "message": "Visualizar todas as opções de login" }, "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Uma notificação foi enviada para o seu dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Certifique-se que sua conta esteja desbloqueada e que a frase de identificação corresponda à do outro dispositivo" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Você será notificado assim que a requisição for aprovada" + }, + "needAnotherOptionV1": { + "message": "Precisa de outra opção?" + }, "loginInitiated": { "message": "Login iniciado" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Abrir em uma nova janela" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Lembrar deste dispositivo para permanecer conectado" + }, "deviceApprovalRequired": { "message": "Aprovação do dispositivo necessária. Selecione uma opção de aprovação abaixo:" }, + "deviceApprovalRequiredV2": { + "message": "Aprovação do dispositivo necessária" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecione uma opção de aprovação abaixo" + }, "rememberThisDevice": { "message": "Lembrar deste dispositivo" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "E-mail do usuário ausente" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mail de usuário ativo não encontrado. Desconectando." + }, "deviceTrusted": { "message": "Dispositivo confiável" }, @@ -3521,6 +3506,14 @@ "message": "Desbloqueie sua conta, abra em uma nova janela", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "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", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Preencha as credenciais para", "description": "Screen reader text for when overlay item is in focused" @@ -3747,7 +3740,10 @@ "message": "Chave de acesso" }, "accessing": { - "message": "Accessing" + "message": "Acessando" + }, + "loggedInExclamation": { + "message": "Sessão Iniciada!" }, "passkeyNotCopied": { "message": "A chave de acesso não será copiada" @@ -3774,7 +3770,7 @@ "message": "Sem credenciais correspondentes para este site" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Pesquisar ou salvar senha como novo login" }, "confirm": { "message": "Confirmar" @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtros" }, + "filterVault": { + "message": "Filtrar cofre" + }, + "filterApplied": { + "message": "Um filtro aplicado" + }, + "filterAppliedPlural": { + "message": "Foram aplicados $COUNT$ filtros", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Detalhes pessoais" }, @@ -4564,13 +4575,13 @@ "message": "Localização do Item" }, "fileSend": { - "message": "File Send" + "message": "Arquivo enviado" }, "fileSends": { "message": "Arquivos enviados" }, "textSend": { - "message": "Text Send" + "message": "Enviar texto" }, "textSends": { "message": "Texto enviado" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Mostrar o número de sugestões de preenchimento automático de login no ícone da extensão" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "Padrão do sistema" }, "enterprisePolicyRequirementsApplied": { "message": "Os requisitos de política empresarial foram aplicados nesta configuração" }, + "sshPrivateKey": { + "message": "Chave privada" + }, + "sshPublicKey": { + "message": "Chave pública" + }, + "sshFingerprint": { + "message": "Impressão digital" + }, + "sshKeyAlgorithm": { + "message": "Tipo de chave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Tente novamente" }, @@ -4632,103 +4670,130 @@ "noEditPermissions": { "message": "Você não tem permissão para editar este arquivo" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autenticando" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Preencher a senha gerada", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Senha regenerada", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Salvar login no Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Espaço", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Linear", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Bastão", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Ponto de exclamação", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Arroba", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Jogo da velha", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "cifrão", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Porcentagem", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Circunflexo", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "E Comercial", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Asterísco", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Parêntese esquerdo", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Parênteses direito", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Sublinhado", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Hífen", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Mais", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "iguais", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Abre chaves", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Fecha chaves", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Abre colchetes", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Fecha colchetes", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { @@ -4736,56 +4801,107 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Barra Invertida", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Dois Pontos", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Ponto e vírgula", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Aspas duplas", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Aspas simples", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Menor que", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Maior que", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Virgula", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Ponto", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Ponto de interrogação", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Barra", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Letras minúsculas" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Letras maiúsculas" }, "generatedPassword": { - "message": "Generated password" + "message": "Senha gerada" + }, + "compactMode": { + "message": "Modo compacto" + }, + "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" + }, + "wide": { + "message": "Grande" + }, + "extraWide": { + "message": "Extra Grande" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 3885f6bad64..6b3c190f0b5 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copiar número da carta de condução" }, + "copyPrivateKey": { + "message": "Copiar chave privada" + }, + "copyPublicKey": { + "message": "Copiar chave pública" + }, + "copyFingerprint": { + "message": "Copiar impressão digital" + }, "copyCustomField": { "message": "Copiar $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Preencher automaticamente identidade" }, + "fillVerificationCode": { + "message": "Preencher código de verificação" + }, + "fillVerificationCodeAria": { + "message": "Preencher código de verificação", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Gerar palavra-passe (copiada)" }, @@ -438,9 +454,6 @@ "length": { "message": "Comprimento" }, - "passwordMinLength": { - "message": "Comprimento mínimo da palavra-passe" - }, "uppercase": { "message": "Maiúsculas (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Mínimo de caracteres especiais" }, - "avoidAmbChar": { - "message": "Evitar caracteres ambíguos", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Evitar caracteres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." @@ -552,7 +561,7 @@ "message": "Frase de acesso" }, "favorite": { - "message": "Favorito" + "message": "Adicionar aos favoritos" }, "unfavorite": { "message": "Remover dos favoritos" @@ -632,9 +641,6 @@ "rateExtension": { "message": "Avaliar a extensão" }, - "rateExtensionDesc": { - "message": "Por favor, considere ajudar-nos com uma boa avaliação!" - }, "browserNotSupportClipboard": { "message": "O seu navegador Web não suporta a cópia fácil da área de transferência. Em vez disso, copie manualmente." }, @@ -797,7 +803,7 @@ "message": "Código de verificação inválido" }, "valueCopied": { - "message": "$VALUE$ copiado", + "message": "$VALUE$ copiado(a)", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Listar itens de identidades na página Separador para facilitar o preenchimento automático." }, + "clickToAutofillOnVault": { + "message": "Clique nos itens para preencher automaticamente na vista do cofre" + }, "clearClipboard": { "message": "Limpar área de transferência", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "AVISO", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Aviso", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirmar a exportação do cofre" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Mover para a organização" }, - "share": { - "message": "Partilhar" - }, "movedItemToOrg": { "message": "$ITEMNAME$ movido para $ORGNAME$", "placeholders": { @@ -1256,7 +1266,7 @@ "message": "Pode adquirir uma subscrição Premium no cofre web em bitwarden.com. Pretende visitar o site agora?" }, "premiumPurchaseAlertV2": { - "message": "Pode adquirir o Premium a partir das definições da sua conta na aplicação Web do Bitwarden." + "message": "Pode adquirir o Premium a partir das definições da sua conta na aplicação Web Bitwarden." }, "premiumCurrentMember": { "message": "É um membro Premium!" @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Introduza o código de verificação de 6 dígitos da sua aplicação de autenticação." }, + "authenticationTimeout": { + "message": "Tempo limite de autenticação" + }, + "authenticationSessionTimedOut": { + "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de início de sessão." + }, "enterVerificationCodeEmail": { "message": "Introduza o código de verificação de 6 dígitos que foi enviado por e-mail para $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Se for detetado um formulário de início de sessão, o preenchimento automático é efetuado quando a página Web é carregada." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Aviso:$CLOSETAG$ Aviso: Os sites comprometidos ou não confiáveis podem explorar o preenchimento automático ao carregar a página.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Os sites comprometidos ou não confiáveis podem explorar o preenchimento automático ao carregar a página." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identidade" }, + "typeSshKey": { + "message": "Chave SSH" + }, "newItemHeader": { "message": "Novo(a) $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Notas seguras" }, + "sshKeys": { + "message": "Chaves SSH" + }, "clear": { "message": "Limpar", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Duplicar" }, - "passwordGeneratorPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão a afetar as suas definições do gerador." - }, "passwordGenerator": { "message": "Gerador de palavras-passe" }, @@ -2318,6 +2324,9 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domínios bloqueados" + }, "excludedDomains": { "message": "Domínios excluídos" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não pedirá para guardar os detalhes de início de sessão destes domínios para todas as contas com sessão iniciada. É necessário atualizar a página para que as alterações tenham efeito." }, + "blockedDomainsDesc": { + "message": "O preenchimento automático e outras funcionalidades relacionadas não serão disponibilizados para estes sites. É necessário atualizar a página para que as alterações tenham efeito." + }, + "autofillBlockedNotice": { + "message": "O preenchimento automático está bloqueado para este site. Reveja ou altere esta opção nas definições." + }, + "autofillBlockedTooltip": { + "message": "O preenchimento automático está bloqueado neste site. Reveja nas definições." + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Alterações do domínio bloqueado guardadas" + }, "excludedDomainsSavedSuccess": { "message": "Alterações do domínio excluído guardadas" }, @@ -2373,14 +2394,6 @@ "message": "Detalhes do Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Procurar Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Adicionar Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Texto" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Ocultar texto por predefinição" }, - "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." - }, "expired": { "message": "Expirado" }, - "pendingDeletion": { - "message": "Eliminação pendente" - }, "passwordProtected": { "message": "Protegido por palavra-passe" }, @@ -2456,24 +2462,9 @@ "message": "Editar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Que tipo de Send é este?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Um nome simpático para descrever este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "O ficheiro que deseja enviar." - }, "deletionDate": { "message": "Data de eliminação" }, - "deletionDateDesc": { - "message": "O Send será permanentemente eliminado na data e hora especificadas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "O Send será permanentemente eliminado nesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Prazo de validade" }, - "expirationDateDesc": { - "message": "Se definido, o acesso a este Send expirará na data e hora especificadas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dia" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalizado" }, - "maximumAccessCount": { - "message": "Número máximo de acessos" - }, - "maximumAccessCountDesc": { - "message": "Se definido, os utilizadores deixarão de poder aceder a este Send quando a contagem máxima de acessos for atingida.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Opcionalmente, exigir uma palavra-passe para os utilizadores acederem a este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Adicione uma palavra-passe opcional para os destinatários acederem a este Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Notas privadas sobre este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Desative este Send para que ninguém possa aceder ao mesmo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copiar o link deste Send para a área de transferência ao guardar.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "O texto que deseja enviar." - }, - "sendHideText": { - "message": "Ocultar o texto deste Send por defeito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Número de acessos atual" - }, "createSend": { "message": "Novo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Antes de começar" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Para utilizar um seletor de datas do tipo calendário,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "clique aqui", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "para abrir a janela.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "O prazo de validade fornecido não é válido." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Ocorreu um erro ao guardar as suas datas de eliminação e validade." }, - "hideEmail": { - "message": "Ocultar o meu endereço de e-mail dos destinatários." - }, "hideYourEmail": { "message": "Oculte o seu endereço de e-mail dos visualizadores." }, - "sendOptionsPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão a afetar as suas opções do Send." - }, "passwordPrompt": { "message": "Pedir novamente a palavra-passe mestra" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Erro" }, - "regenerateUsername": { - "message": "Regenerar nome de utilizador" + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Gerar nome de utilizador" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Gerar e-mail" }, - "generatorBoundariesHint": { - "message": "O valor deve estar entre $MIN$ e $MAX$", + "spinboxBoundariesHint": { + "message": "O valor deve estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tipo de nome de utilizador" + "passwordLengthRecommendationHint": { + "message": " Utilize $RECOMMENDED$ caracteres ou mais para gerar uma palavra-passe forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilize $RECOMMENDED$ palavras ou mais para gerar uma frase de acesso 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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-mail com subendereço", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nome do site" }, - "whatWouldYouLikeToGenerate": { - "message": "O que é que gostaria de gerar?" - }, - "passwordType": { - "message": "Tipo de palavra-passe" - }, "service": { "message": "Serviço" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Reenviar notificação" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Ver todas as opções de início de sessão" + }, + "viewAllLoginOptionsV1": { "message": "Ver todas as opções de início de sessão" }, "notificationSentDevice": { "message": "Foi enviada uma notificação para o seu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Foi enviada uma notificação para o seu dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Certifique-se de que a sua conta está desbloqueada e que a frase de impressão digital corresponde à do outro dispositivo" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Será notificado quando o pedido for aprovado" + }, + "needAnotherOptionV1": { + "message": "Precisa de outra opção?" + }, "loginInitiated": { "message": "A preparar o início de sessão" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Abrir numa nova janela" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Memorizar este dispositivo para facilitar futuros inícios de sessão" + }, "deviceApprovalRequired": { "message": "É necessária a aprovação do dispositivo. Selecione uma opção de aprovação abaixo:" }, + "deviceApprovalRequiredV2": { + "message": "Aprovação do dispositivo necessária" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecione uma opção de aprovação abaixo" + }, "rememberThisDevice": { "message": "Lembrar este dispositivo" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "E-mail do utilizador em falta" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "O e-mail do utilizador ativo não foi encontrado. A terminar a sessão." + }, "deviceTrusted": { "message": "Dispositivo de confiança" }, @@ -3521,6 +3506,14 @@ "message": "Desbloqueie a sua conta, abre numa nova janela", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Código de verificação de palavra-passe única com base no tempo", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Tempo restante antes da TOTP atual expirar", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Preencher as credenciais para", "description": "Screen reader text for when overlay item is in focused" @@ -3747,7 +3740,10 @@ "message": "Chave de acesso" }, "accessing": { - "message": "A aceder" + "message": "A aceder a" + }, + "loggedInExclamation": { + "message": "Sessão iniciada!" }, "passkeyNotCopied": { "message": "A chave de acesso não será copiada" @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtros" }, + "filterVault": { + "message": "Filtrar cofre" + }, + "filterApplied": { + "message": "Um filtro aplicado" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtros aplicados", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Dados pessoais" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Mostrar o número de sugestões de preenchimento automático de credenciais no ícone da extensão" }, + "showQuickCopyActions": { + "message": "Mostrar ações de cópia rápida no cofre" + }, "systemDefault": { "message": "Predefinição do sistema" }, "enterprisePolicyRequirementsApplied": { "message": "Os requisitos da política empresarial foram aplicados a esta definição" }, + "sshPrivateKey": { + "message": "Chave privada" + }, + "sshPublicKey": { + "message": "Chave pública" + }, + "sshFingerprint": { + "message": "Impressão digital" + }, + "sshKeyAlgorithm": { + "message": "Tipo de chave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Tentar novamente" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Não tem permissão para editar este item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "O desbloqueio biométrico não está disponível porque a app para computador Bitwarden está fechada." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, "authenticating": { "message": "A autenticar" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Palavra-passe gerada" + }, + "compactMode": { + "message": "Modo compacto" + }, + "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" + }, + "wide": { + "message": "Ampla" + }, + "extraWide": { + "message": "Muito ampla" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index f25897f38f6..114c01aff44 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copiați numărul de licență" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copiază $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autocompletare identitate" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generare parolă (s-a copiat)" }, @@ -438,9 +454,6 @@ "length": { "message": "Lungime" }, - "passwordMinLength": { - "message": "Lungimea minimă a parolei" - }, "uppercase": { "message": "Litere mari (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minim de caractere speciale" }, - "avoidAmbChar": { - "message": "Se evită caracterele ambigue", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Evaluare extensie" }, - "rateExtensionDesc": { - "message": "Vă rugăm să luați în considerare să ne ajutați cu o recenzie bună!" - }, "browserNotSupportClipboard": { "message": "Browserul dvs. nu acceptă copierea în clipboard. Transcrieți datele manual." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Listați elementelor de identitate de pe pagina Filă pentru a facilita completarea automată." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Golire clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "AVERTISMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirmare export seif" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Mutare la organizație" }, - "share": { - "message": "Partajare" - }, "movedItemToOrg": { "message": "$ITEMNAME$ mutat la $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Introducere cod de verificare din 6 cifre din aplicația de autentificare." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Introducere cod de verificare din 6 cifre care a fost trimis prin e-mail la $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Dacă se detectează un formular de autentificare, completați-l automat la încărcarea paginii web." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Site-urile web compromise sau nesigure pot profita de auto-completarea la încărcare." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitate" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Note securizate" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Ștergere", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clonare" }, - "passwordGeneratorPolicyInEffect": { - "message": "Una sau mai multe politici organizaționale vă afectează setările generatorului." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domenii excluse" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Căutare Send-uri", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Adăugare Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "S-a atins numărul maxim de accesări", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expirat" }, - "pendingDeletion": { - "message": "Ștergere în așteptare" - }, "passwordProtected": { "message": "Protejat cu parolă" }, @@ -2456,24 +2462,9 @@ "message": "Editare Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Ce fel de Send este acesta?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nume prietenos pentru a descrie acest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Fișierul pe care doriți să-l trimiteți." - }, "deletionDate": { "message": "Data ștergerii" }, - "deletionDateDesc": { - "message": "Send-ul va fi șters definitiv la data și ora specificate.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Data expirării" }, - "expirationDateDesc": { - "message": "Dacă este setat, accesul la acest Send va expira la data și ora specificate.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 zi" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Personalizat" }, - "maximumAccessCount": { - "message": "Număr maxim de accesări" - }, - "maximumAccessCountDesc": { - "message": "Dacă este configurat, utilizatorii nu vor mai putea accesa acest Send când a fost atins numărul maxim de accesări.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Opțional, este necesară o parolă pentru ca utilizatorii să acceseze acest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Note private despre acest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Dezactivați acest Send pentru ca nimeni să nu-l poată accesa.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copiază acest link de Send în clipboard la salvare.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Textul pe care doriți să-l trimiteți." - }, - "sendHideText": { - "message": "Ascunde în mod implicit textul acestui Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Numărul actual de accesări" - }, "createSend": { "message": "Nou Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Înainte de a începe" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Pentru a utiliza un selector de date în stil calendar,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "faceți clic aici", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "pentru ca fereastra dvs să apară.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Data de expirare furnizată nu este validă." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "A survenit o eroare la salvarea datelor de ștergere și de expirare." }, - "hideEmail": { - "message": "Ascundeți adresa mea de e-mail de la destinatari." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Una sau mai multe politici organizaționale vă afectează opțiunile Send-ului." - }, "passwordPrompt": { "message": "Re-solicitare parolă principală" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Eroare" }, - "regenerateUsername": { - "message": "Regenerare nume de utilizator" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generare nume de utilizator" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Tip de nume de utilizator" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-mail Plus adresat", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Nume website" }, - "whatWouldYouLikeToGenerate": { - "message": "Ce doriți să generați?" - }, - "passwordType": { - "message": "Tip de parolă" - }, "service": { "message": "Serviciu" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Reîntoarceți notificarea" }, - "viewAllLoginOptions": { - "message": "Afișați toate opțiunile de conectare" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "O notificare a fost trimisă pe dispozitivul dvs." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Conectare inițiată" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Se deschide într-o nouă fereastră" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Este necesară aprobarea dispozitivului. Selectați o opțiune de autorizare de mai jos:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Memorizează acest dispozitiv" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Lipsește e-mailul utilizatorului" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispozitiv de încredere" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 7c7a70c82ff..53f31813ac5 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Скопировать номер лицензии" }, + "copyPrivateKey": { + "message": "Скопировать приватный ключ" + }, + "copyPublicKey": { + "message": "Скопировать публичный ключ" + }, + "copyFingerprint": { + "message": "Скопировать отпечаток" + }, "copyCustomField": { "message": "Скопировать $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Автозаполнение личности" }, + "fillVerificationCode": { + "message": "Заполнить код подтверждения" + }, + "fillVerificationCodeAria": { + "message": "Заполнить код подтверждения", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Сгенерировать пароль (с копированием)" }, @@ -438,9 +454,6 @@ "length": { "message": "Длина" }, - "passwordMinLength": { - "message": "Минимальная длина пароля" - }, "uppercase": { "message": "Прописные буквы (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Минимум символов" }, - "avoidAmbChar": { - "message": "Избегать неоднозначных символов", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Избегать неоднозначных символов", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Оценить расширение" }, - "rateExtensionDesc": { - "message": "Пожалуйста, подумайте о том, чтобы помочь нам хорошим отзывом!" - }, "browserNotSupportClipboard": { "message": "Ваш браузер не поддерживает копирование данных в буфер обмена. Скопируйте вручную." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Личности будут отображены на вкладке для удобного автозаполнения." }, + "clickToAutofillOnVault": { + "message": "Кликните элементы для автозаполнения в режиме просмотра хранилища" + }, "clearClipboard": { "message": "Очистить буфер обмена", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ВНИМАНИЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Предупреждение", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Подтвердить экспорт хранилища" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Переместить в организацию" }, - "share": { - "message": "Поделиться" - }, "movedItemToOrg": { "message": "$ITEMNAME$ перемещен в $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Введите 6-значный код подтверждения из вашего приложения-аутентификатора." }, + "authenticationTimeout": { + "message": "Таймаут аутентификации" + }, + "authenticationSessionTimedOut": { + "message": "Сеанс аутентификации завершился по времени. Пожалуйста, попробуйте войти еще раз." + }, "enterVerificationCodeEmail": { "message": "Введите 6-значный код подтверждения, который был отправлен на $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Если обнаружена форма входа, автозаполнение выполняется при загрузке веб-страницы." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Предупреждение:$CLOSETAG$ взломанные или недоверенные сайты могут использовать автозаполнение при загрузке страницы.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Взломанные или недоверенные сайты могут внедрить вредоносный код во время автозаполнения при загрузке страницы." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Личная информация" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "newItemHeader": { "message": "Новый $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Защищенные заметки" }, + "sshKeys": { + "message": "Ключи SSH" + }, "clear": { "message": "Очистить", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Клонировать" }, - "passwordGeneratorPolicyInEffect": { - "message": "На настройки генератора влияют одна или несколько политик организации." - }, "passwordGenerator": { "message": "Генератор паролей" }, @@ -2318,6 +2324,9 @@ "message": "Домены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблокированные домены" + }, "excludedDomains": { "message": "Исключенные домены" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не будет предлагать сохранение логинов для этих доменов для всех авторизованных аккаунтов. Для вступления изменений в силу необходимо обновить страницу." }, + "blockedDomainsDesc": { + "message": "Автозаполнение и другие связанные с ним функции не будут предлагаться для этих сайтов. Чтобы изменения вступили в силу, необходимо обновить страницу." + }, + "autofillBlockedNotice": { + "message": "Автозаполнение для этого сайта заблокировано. Просмотрите или измените это в настройках." + }, + "autofillBlockedTooltip": { + "message": "Автозаполнение на этом сайте заблокировано. Просмотрите в настройках." + }, "websiteItemLabel": { "message": "Сайт $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Изменения в заблокированном домене сохранены" + }, "excludedDomainsSavedSuccess": { "message": "Изменения в исключенном домене сохранены" }, @@ -2373,14 +2394,6 @@ "message": "Информация о Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Поиск Send’ов", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Добавить Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Текст" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Скрыть текст по умолчанию" }, - "maxAccessCountReached": { - "message": "Достигнут максимум обращений", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Срок истек" }, - "pendingDeletion": { - "message": "Ожидание удаления" - }, "passwordProtected": { "message": "Защищено паролем" }, @@ -2456,24 +2462,9 @@ "message": "Изменить Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Выберите тип Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Понятное имя для описания этой Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Файл, который вы хотите отправить." - }, "deletionDate": { "message": "Дата удаления" }, - "deletionDateDesc": { - "message": "Эта Send будет окончательно удалена в указанные дату и время.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "С этой даты Send будет удалена навсегда.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Дата истечения" }, - "expirationDateDesc": { - "message": "Если задано, доступ к этой Send истечет в указанные дату и время.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 день" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Пользовательский" }, - "maximumAccessCount": { - "message": "Максимум обращений" - }, - "maximumAccessCountDesc": { - "message": "Если задано, пользователи больше не смогут получить доступ к этой Send, как только будет достигнуто максимальное количество обращений.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "По возможности запрашивать у пользователей пароль для доступа к этой Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Добавьте опциональный пароль для доступа получателей к этой Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Личные заметки об этой Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Деактивировать эту Send, чтобы никто не мог получить к ней доступ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Скопировать ссылку на эту Send в буфер обмена после сохранения.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст, который вы хотите отправить." - }, - "sendHideText": { - "message": "Скрыть текст этой Send по умолчанию.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Текущих обращений" - }, "createSend": { "message": "Новая Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Перед тем, как начать" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Для использования календарного стиля выбора даты,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "нажмите здесь", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "для открытия в новом окне.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Срок истечения указан некорректно." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Произошла ошибка при сохранении данных о сроках удаления и истечения." }, - "hideEmail": { - "message": "Скрыть мой адрес email от получателей." - }, "hideYourEmail": { "message": "Скрыть ваш email от просматривающих." }, - "sendOptionsPolicyInEffect": { - "message": "На параметры Send влияют одна или несколько политик организации." - }, "passwordPrompt": { "message": "Повторный запрос мастер-пароля" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Ошибка" }, - "regenerateUsername": { - "message": "Пересоздать имя пользователя" + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Создать имя пользователя" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Сгенерировать email" }, - "generatorBoundariesHint": { - "message": "Значение должно быть между $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Значение должно быть между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Тип имени пользователя" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Субадресованные email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Название сайта" }, - "whatWouldYouLikeToGenerate": { - "message": "Что вы хотите сгенерировать?" - }, - "passwordType": { - "message": "Тип пароля" - }, "service": { "message": "Служба" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Отправить уведомление повторно" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Посмотреть все варианты авторизации" + }, + "viewAllLoginOptionsV1": { "message": "Посмотреть все варианты авторизации" }, "notificationSentDevice": { "message": "На ваше устройство отправлено уведомление." }, + "aNotificationWasSentToYourDevice": { + "message": "На ваше устройство было отправлено уведомление" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Убедитесь, что ваш аккаунт разблокирован и фраза отпечатка совпадает с фразой на другом устройстве" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Вы получите уведомление, когда запрос будет одобрен" + }, + "needAnotherOptionV1": { + "message": "Нужен другой вариант?" + }, "loginInitiated": { "message": "Вход инициирован" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Откроется в новом окне" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запомнить это устройство, чтобы в будущем авторизовываться быстрее" + }, "deviceApprovalRequired": { "message": "Требуется одобрение устройства. Выберите вариант ниже:" }, + "deviceApprovalRequiredV2": { + "message": "Требуется подтверждение устройства" + }, + "selectAnApprovalOptionBelow": { + "message": "Выберите вариант подтверждения ниже" + }, "rememberThisDevice": { "message": "Запомнить это устройство" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Отсутствует email пользователя" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Email активного пользователя не найден. Разлогиниваем." + }, "deviceTrusted": { "message": "Доверенное устройство" }, @@ -3521,6 +3506,14 @@ "message": "Разблокируйте ваш аккаунт, откроется в новом окне", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Код подтверждения, основанный на времени", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Время, оставшееся до истечения срока действия текущего TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Заполнить учетные данные", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Доступ" }, + "loggedInExclamation": { + "message": "Выполнен вход!" + }, "passkeyNotCopied": { "message": "Passkey не будет скопирован" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Фильтры" }, + "filterVault": { + "message": "Фильтр хранилища" + }, + "filterApplied": { + "message": "Применен один фильтр" + }, + "filterAppliedPlural": { + "message": "Применено фильтров: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Личные данные" }, @@ -4582,17 +4593,44 @@ "message": "Теперь автозаполнение и поиск на вкладке Хранилище стали проще и интуитивно понятнее, чем когда-либо. Осмотритесь!" }, "accountActions": { - "message": "Действия с аккаунтом" + "message": "Действия аккаунта" }, "showNumberOfAutofillSuggestions": { "message": "Показывать количество вариантов автозаполнения логина на значке расширения" }, + "showQuickCopyActions": { + "message": "Показать быстрые действия копирования в хранилище" + }, "systemDefault": { "message": "Системный" }, "enterprisePolicyRequirementsApplied": { "message": "К этой настройке были применены требования корпоративной политики" }, + "sshPrivateKey": { + "message": "Приватный ключ" + }, + "sshPublicKey": { + "message": "Публичный ключ" + }, + "sshFingerprint": { + "message": "Отпечаток" + }, + "sshKeyAlgorithm": { + "message": "Тип ключа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Повторить" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "У вас нет разрешения на редактирование этого элемента" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Биометрическая разблокировка недоступна, поскольку Bitwarden для компьютера закрыт." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authenticating": { "message": "Аутентификация" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Сгенерированный пароль" + }, + "compactMode": { + "message": "Компактный режим" + }, + "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": "Ширина расширения" + }, + "wide": { + "message": "Широкое" + }, + "extraWide": { + "message": "Очень широкое" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 4dd1bfeb898..56cf378344f 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "මුරපදය ජනනය (පිටපත්)" }, @@ -438,9 +454,6 @@ "length": { "message": "දිග" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "අවම විශේෂ" }, - "avoidAmbChar": { - "message": "අපැහැදිලි චරිත වලින් වළකින්න", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "දිගුව අනුපාතය" }, - "rateExtensionDesc": { - "message": "කරුණාකර හොඳ සමාලෝචනයකින් අපට උදව් කිරීම ගැන සලකා බලන්න!" - }, "browserNotSupportClipboard": { "message": "ඔබේ වෙබ් බ්රව්සරය පහසු පසුරු පුවරුවක් පිටපත් කිරීමට සහාය නොදක්වයි. ඒ වෙනුවට එය අතින් පිටපත් කරන්න." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "පසුරු පුවරුවට පැහැදිලි", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "අවවාදයයි", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "සුරක්ෂිතාගාරය අපනයන තහවුරු" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "සංවිධානය වෙත ගෙනයන්න" }, - "share": { - "message": "බෙදාගන්න" - }, "movedItemToOrg": { "message": "$ITEMNAME$ $ORGNAME$වෙත ගෙන ගියේය", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "ඔබගේ සත්යාපන යෙදුමෙන් 6 ඉලක්කම් සත්යාපන කේතය ඇතුළත් කරන්න." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$වෙත ඊමේල් කරන ලද 6 ඉලක්කම් සත්යාපන කේතය ඇතුළත් කරන්න.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "පිවිසුම් පෝරමයක් අනාවරණය කර ඇත්නම්, වෙබ් පිටුව පැටවුම් කරන විට ස්වයංක්රීයව ස්වයංක්රීයව පිරවීම සිදු කරන්න." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "අනන්යතාවය" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "ආරක්ෂිත සටහන්" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "පැහැදිලි", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "ක්ලෝන" }, - "passwordGeneratorPolicyInEffect": { - "message": "සංවිධාන ප්රතිපත්ති එකක් හෝ වැඩි ගණනක් ඔබේ උත්පාදක සැකසුම් වලට බලපායි." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "බැහැර වසම්" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "සෙවුම් යවයි", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "යවන්න එකතු කරන්න", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "පෙළ" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "මැක්ස් ප්රවේශ ගණන ළඟා", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "කල් ඉකුත්" }, - "pendingDeletion": { - "message": "මකාදැමීම" - }, "passwordProtected": { "message": "මුරපදය ආරක්ෂා" }, @@ -2456,24 +2462,9 @@ "message": "යැවීම සංස්කරණය", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "මෙය කුමන ආකාරයේ යවන්න ද?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "මෙම යවන්න විස්තර කිරීමට මිත්රශීලී නමක්.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "ඔබට යැවීමට අවශ්ය ගොනුව." - }, "deletionDate": { "message": "මකාදැමීමේ දිනය" }, - "deletionDateDesc": { - "message": "නියම කරන ලද දිනය හා වේලාව මත Send ස්ථිරවම මකා දමනු ලැබේ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "කල් ඉකුත්වන දිනය" }, - "expirationDateDesc": { - "message": "සකසා ඇත්නම්, මෙම යවන්න වෙත ප්රවේශය නිශ්චිත දිනය හා වේලාව කල් ඉකුත් වනු ඇත.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "දින 1" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "අභිරුචි" }, - "maximumAccessCount": { - "message": "උපරිම ප්රවේශ ගණන්" - }, - "maximumAccessCountDesc": { - "message": "සකසා ඇත්නම්, උපරිම ප්රවේශ ගණන ළඟා වූ පසු පරිශීලකයින්ට මෙම Send වෙත ප්රවේශ වීමට තවදුරටත් නොහැකි වනු ඇත.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "විකල්පයක් ලෙස පරිශීලකයින්ට මෙම යවන්න වෙත ප්රවේශ වීමට මුරපදයක් අවශ්ය වේ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "මේ ගැන පෞද්ගලික සටහන් යවන්න.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "මෙය අක්රීය කරන්න යවන්න එවිට කිසිවෙකුට එයට ප්රවේශ විය නොහැක.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "සුරකින්න මත මෙම යවන්න ගේ සබැඳිය පසුරු පුවරුවට පිටපත් කරන්න.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "ඔබට යැවීමට අවශ්ය පෙළ." - }, - "sendHideText": { - "message": "මෙම යවන්න පෙළ පෙරනිමියෙන් සඟවන්න.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "වත්මන් ප්රවේශ ගණන්" - }, "createSend": { "message": "නව යවන්න නිර්මාණය", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "ඔබ ආරම්භ කිරීමට පෙර" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "දින දර්ශනය ශෛලිය දිනය ජීව අත්බෝම්බයක් සමග භාවිතා කිරීමට", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "මෙහි ක්ලික් කරන්න", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "ඔබේ කවුළුව දිස්වේ.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "ලබා දී ඇති කල් ඉකුත්වන දිනය වලංගු නොවේ." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "ඔබගේ මකාදැමීම සහ කල් ඉකුත් වීමේ දිනයන් ඉතිරි කිරීමේ දෝෂයක් තිබුණි." }, - "hideEmail": { - "message": "ලබන්නන්ගෙන් මගේ විද්යුත් තැපැල් ලිපිනය සඟවන්න." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "සංවිධාන ප්රතිපත්ති එකක් හෝ කිහිපයක් ඔබගේ Send විකල්පයන්ට බලපායි." - }, "passwordPrompt": { "message": "ප්රධාන මුරපදය නැවත විමසුමක්" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index dc46b0fdb85..08bfcc79f6a 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Kopírovať číslo licencie" }, + "copyPrivateKey": { + "message": "Kopírovať súkromný kľúč" + }, + "copyPublicKey": { + "message": "Kopírovať verejný kľúč" + }, + "copyFingerprint": { + "message": "Kopírovať odtlačok" + }, "copyCustomField": { "message": "Kopírovať $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Automatické vyplnenie identity" }, + "fillVerificationCode": { + "message": "Vyplniť overovací kód" + }, + "fillVerificationCodeAria": { + "message": "Vyplniť overovací kód", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Vygenerovať heslo (skopírované)" }, @@ -438,9 +454,6 @@ "length": { "message": "Dĺžka" }, - "passwordMinLength": { - "message": "Minimálna dĺžka hesla" - }, "uppercase": { "message": "Veľké písmená (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum špeciálnych znakov" }, - "avoidAmbChar": { - "message": "Vyhnúť sa zameniteľným znakom", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Vyhnúť sa zameniteľným znakom", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Ohodnotiť rozšírenie" }, - "rateExtensionDesc": { - "message": "Prosíme, zvážte napísanie pozitívnej recenzie!" - }, "browserNotSupportClipboard": { "message": "Váš webový prehliadač nepodporuje automatické kopírovanie do schránky. Kopírujte manuálne." }, @@ -846,7 +852,7 @@ "message": "Prihlásiť sa" }, "logInToBitwarden": { - "message": "Prihlásiť sa do Bitwardenu" + "message": "Prihlásenie do Bitwardenu" }, "restartRegistration": { "message": "Zopakovať registráciu" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Zoznam položiek identity na stránke \"Aktuálna karta\" na jednoduché automatické vypĺňanie." }, + "clickToAutofillOnVault": { + "message": "Kliknutím na položku v trezore automaticky vyplniť" + }, "clearClipboard": { "message": "Vymazať schránku", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "UPOZORNENIE", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Upozornenie", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Potvrdiť export trezoru" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Presunúť do organizácie" }, - "share": { - "message": "Zdieľať" - }, "movedItemToOrg": { "message": "$ITEMNAME$ presunuté do $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Zadajte 6-miestny verifikačný kód z vašej overovacej aplikácie." }, + "authenticationTimeout": { + "message": "Časový limit overenia" + }, + "authenticationSessionTimedOut": { + "message": "Relácia overovania skončila. Znovu spustite proces prihlásenia." + }, "enterVerificationCodeEmail": { "message": "Zadajte 6-miestny verifikačný kód, ktorý vám bol zaslaný emailom", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Ak je detekovaný prihlasovací formulár, automaticky vykonať vypĺňanie pri načítaní stránky." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Upozornenie:$CLOSETAG$ Kompromitované alebo nedôveryhodné webové stránky môžu využívať automatické vypĺňanie pri načítaní stránky.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Skompromitované alebo nedôveryhodné stránky môžu pri svojom načítaní zneužiť automatické dopĺňanie." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identita" }, + "typeSshKey": { + "message": "Kľúč SSH" + }, "newItemHeader": { "message": "Nové $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Zabezpečené poznámky" }, + "sshKeys": { + "message": "Kľúče SSH" + }, "clear": { "message": "Vyčistiť", "description": "To clear something out. example: To clear browser history." @@ -1920,7 +1929,7 @@ "message": "Vymazať históriu" }, "nothingToShow": { - "message": "Nič na zobrazenie" + "message": "Nie je čo zobraziť" }, "nothingGeneratedRecently": { "message": "V poslednej dobe ste nič negenerovali" @@ -2031,9 +2040,6 @@ "clone": { "message": "Klonovať" }, - "passwordGeneratorPolicyInEffect": { - "message": "Jedno alebo viac nastavení organizácie ovplyvňujú vaše nastavenia generátora." - }, "passwordGenerator": { "message": "Generátor hesla" }, @@ -2318,6 +2324,9 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, "excludedDomains": { "message": "Vylúčené domény" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude požadovať ukladanie prihlasovacích údajov pre tieto domény pre všetky prihlásené účty. Aby sa zmeny prejavili, musíte stránku obnoviť." }, + "blockedDomainsDesc": { + "message": "Automatické vypĺňanie a ďalšie súvisiace funkcie sa na týchto webových stránkach nebudú ponúkať. Aby sa zmeny prejavili, musíte stránku obnoviť." + }, + "autofillBlockedNotice": { + "message": "Automatické vypĺňanie je pre túto webovú stránku zablokované. Skontrolujte alebo zmeňte to v nastaveniach." + }, + "autofillBlockedTooltip": { + "message": "Automatické vypĺňanie je na tejto webovej stránke zablokované. Pozrite v nastaveniach." + }, "websiteItemLabel": { "message": "Webstránka $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Zmeny v blokovaných doménach boli uložené" + }, "excludedDomainsSavedSuccess": { "message": "Uložené zmeny vylúčenej domény" }, @@ -2373,14 +2394,6 @@ "message": "Podrobnosti o Sende", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Hľadať Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Pridať Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "V predvolenom nastavení skryť text" }, - "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." - }, "expired": { "message": "Expirované" }, - "pendingDeletion": { - "message": "Čakajúce odstránenie" - }, "passwordProtected": { "message": "Chránené heslom" }, @@ -2456,24 +2462,9 @@ "message": "Upraviť Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Aký typ Sendu to je?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Priateľský názov pre popísanie tohto Sendu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Súbor, ktorý chcete odoslať." - }, "deletionDate": { "message": "Dátum odstránenia" }, - "deletionDateDesc": { - "message": "Send bude natrvalo odstránený v zadaný dátum a čas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send bude natrvalo odstránený v tento deň.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Dátum exspirácie" }, - "expirationDateDesc": { - "message": "Ak je nastavené, prístup k tomuto Sendu vyprší v zadaný dátum a čas.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 deň" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Vlastné" }, - "maximumAccessCount": { - "message": "Maximálny počet prístupov" - }, - "maximumAccessCountDesc": { - "message": "Ak je nastavené, používatelia už nebudú mať prístup k tomuto Sendu po dosiahnutí maximálneho počtu prístupov.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Voliteľne môžete vyžadovať heslo pre používateľov na prístup k tomuto Sendu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Pridajte voliteľné heslo pre príjemcov na prístup k tomuto Sendu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Zabezpečená poznámka o tomto Sende.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Vypnúť tento Send, aby k nemu nikto nemal prístup.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Pri uložení kopírovať odkaz na Send do schránky.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Text, ktorý chcete odoslať." - }, - "sendHideText": { - "message": "Predvolene skryť text tohto Sendu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Súčasný počet prístupov" - }, "createSend": { "message": "Vytvoriť nový Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Skôr než začnete" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Ak chcete použiť pre výber dátumu štýl kalendára", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "kliknite sem", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "a vysunie sa.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Uvedený dátum exspirácie nie je platný." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Pri ukladaní dátumov odstránenia a vypršania platnosti sa vyskytla chyba." }, - "hideEmail": { - "message": "Skryť moju emailovú adresu pred príjemcami." - }, "hideYourEmail": { "message": "Skryť moju e-mailovú adresu pri zobrazení." }, - "sendOptionsPolicyInEffect": { - "message": "Jedno alebo viac pravidiel organizácie ovplyvňujú vaše možnosti funkcie Send." - }, "passwordPrompt": { "message": "Znova zadajte hlavné heslo" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Chyba" }, - "regenerateUsername": { - "message": "Vygenerovať nové používateľské meno" + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Vygenerovať používateľské meno" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generovať e-mail" }, - "generatorBoundariesHint": { - "message": "Hodnota musí byť medzi $MIN$ a $MAX$", + "spinboxBoundariesHint": { + "message": "Hodnota musí byť medzi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Typ používateľského mena" + "passwordLengthRecommendationHint": { + "message": " Na vytvorenie silného hesla použite $RECOMMENDED$ znakov alebo viac.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Na vytvorenie silnej prístupovej frázy použite $RECOMMENDED$ slov alebo viac.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "E-mail s plusovým aliasom", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Názov stránky" }, - "whatWouldYouLikeToGenerate": { - "message": "Čo by ste chceli vygenerovať?" - }, - "passwordType": { - "message": "Typ hesla" - }, "service": { "message": "Služba" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Znova odoslať upozornenie" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Zobraziť všetky možnosti prihlásenia" + }, + "viewAllLoginOptionsV1": { "message": "Zobraziť všetky možnosti prihlásenia" }, "notificationSentDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie." }, + "aNotificationWasSentToYourDevice": { + "message": "Do vášho zariadenia bolo odoslané upozornenie" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Uistite sa, že je váš účet odomknutý a fráza odtlačku prsta sa zhoduje s frázou na druhom zariadení" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Po schválení žiadosti budete informovaní" + }, + "needAnotherOptionV1": { + "message": "Potrebujete inú možnosť?" + }, "loginInitiated": { "message": "Iniciované prihlásenie" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Otvárať v novom okne" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamätať si toto zariadenie, pre budúce bezproblémové prihlásenie" + }, "deviceApprovalRequired": { "message": "Vyžaduje sa schválenie zariadenia. Vyberte možnosť schválenia nižšie:" }, + "deviceApprovalRequiredV2": { + "message": "Vyžaduje sa schválenie zariadenia" + }, + "selectAnApprovalOptionBelow": { + "message": "Vyberte možnosť schválenia nižšie" + }, "rememberThisDevice": { "message": "Zapamätať si toto zariadenie" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Chýba e-mail používateľa" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mail aktívneho používateľa sa nenašiel. Odhlasuje sa." + }, "deviceTrusted": { "message": "Dôveryhodné zariadenie" }, @@ -3521,6 +3506,14 @@ "message": "Odomknúť konto v novom okne", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Overovací kód TOTP", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Čas zostávajúci do vypršania aktuálneho TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Vyplňte prihlasovacie údaje pre", "description": "Screen reader text for when overlay item is in focused" @@ -3747,7 +3740,10 @@ "message": "Prístupový kľúč" }, "accessing": { - "message": "Accessing" + "message": "Pristupovanie" + }, + "loggedInExclamation": { + "message": "Prihlásený!" }, "passkeyNotCopied": { "message": "Prístupový kód sa neskopíruje" @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtre" }, + "filterVault": { + "message": "Filtrovať trezor" + }, + "filterApplied": { + "message": "Bol použitý jeden filter" + }, + "filterAppliedPlural": { + "message": "Použitých filtrov ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Osobné údaje" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Zobraziť počet odporúčaných prihlasovacích údajov na ikone rozšírenia" }, + "showQuickCopyActions": { + "message": "Zobraziť akcie rýchleho kopírovania v trezore" + }, "systemDefault": { "message": "Predvolené systémom" }, "enterprisePolicyRequirementsApplied": { "message": "Na toto nastavenie boli uplatnené požiadavky pravidiel spoločnosti" }, + "sshPrivateKey": { + "message": "Súkromný kľúč" + }, + "sshPublicKey": { + "message": "Verejný kľúč" + }, + "sshFingerprint": { + "message": "Odtlačok" + }, + "sshKeyAlgorithm": { + "message": "Typ kľúča" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Znova" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Na úpravu tejto položky nemáte oprávnenie" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože aplikácia Bitwarden pre desktop je zatvorená." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." + }, "authenticating": { "message": "Overuje sa" }, @@ -4652,11 +4717,11 @@ "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilda", + "message": "Vlnovka", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Opačný dĺžeň", + "message": "Obrátená čiarka", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { @@ -4680,7 +4745,7 @@ "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Striežka", + "message": "Strieška", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4732,7 +4797,7 @@ "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Rúra", + "message": "Zvislá čiara", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Vygenerované heslo" + }, + "compactMode": { + "message": "Kompaktný režim" + }, + "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 spoľahlivý prístup k svojmu e-mailu, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nie, nemám" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Áno, mám spoľahlivý prístup k svojmu e-mailu" + }, + "turnOnTwoStepLogin": { + "message": "Zapnúť dvojstupňové prihlásenie" + }, + "changeAcctEmail": { + "message": "Zmeniť e-mail účtu" + }, + "extensionWidth": { + "message": "Šírka rozšírenia" + }, + "wide": { + "message": "Široké" + }, + "extraWide": { + "message": "Extra široké" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index d2af5a49008..2d2ee455415 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Samodejno izpolni identiteto" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generiraj geslo (kopirano)" }, @@ -438,9 +454,6 @@ "length": { "message": "Dolžina" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Velike črke (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimalno posebnih znakov" }, - "avoidAmbChar": { - "message": "Izogibaj se dvoumnim znakom", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Ocenite to razširitev" }, - "rateExtensionDesc": { - "message": "Premislite, ali bi nam želeli pomagati z dobro oceno!" - }, "browserNotSupportClipboard": { "message": "Vaš brskalnik ne podpira enostavnega kopiranja na odložišče. Prosimo, kopirajte ročno." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Na strani Zavihek prikaži elemente identitete za lažje samodejno izpolnjevanje." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Počisti odložišče", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "OPOZORILO", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Potrdite izvoz trezorja" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Premakni v organizacijo" }, - "share": { - "message": "Deli" - }, "movedItemToOrg": { "message": "Element $ITEMNAME$ premaknjen v $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Vnesite 6-mestno verifikacijsko kodo iz svoje aplikacije za avtentikacijo." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Vnesite 6-mestno verifikacijsko kodo, ki vam je bila poslana na $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Če Bitwarden na strani zazna prijavni obrazec, ga samodejno izpolni takoj, ko se stran naloži." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Spletne strani, ki jim ne zaupate ali v katere so vdrli, lahko zlorabijo samodejno izpolnjevanje ob naložitvi strani." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identiteta" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Zavarovani zapiski" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Počisti", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Podvoji" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Izključene domene" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Išči pošiljke", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Dodaj pošiljko", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Besedilo" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Poteklo" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Uredi pošiljko", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Kakšna vrsta pošiljke je to?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Prijazno ime, ki opisuje to pošiljko", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Datoteka, ki jo želite poslati" - }, "deletionDate": { "message": "Datum izbrisa" }, - "deletionDateDesc": { - "message": "Pošiljka bo trajno izbrisana ob izbranem času.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Datum poteka" }, - "expirationDateDesc": { - "message": "Če to nastavite, bo pošiljka potekla ob izbranem času.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dan" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Po meri" }, - "maximumAccessCount": { - "message": "Največje dovoljeno število dostopov" - }, - "maximumAccessCountDesc": { - "message": "Če to nastavite, uporabniki po določenem številu dostopov ne bodo mogli več dostopati do pošiljke.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Za dostop do te pošiljke lahko nastavite geslo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Zasebni zapiski o tej pošiljki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Onemogoči to pošiljko, da nihče ne more dostopati do nje.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopiraj povezavo te pošiljke v odložišče, ko shranim.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Besedilo, ki ga želite poslati" - }, - "sendHideText": { - "message": "Privzeto skrij besedilo te pošiljke.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Trenutno število dstopov" - }, "createSend": { "message": "Nova pošiljka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Preden pričnete" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Za vnos datuma s pomočjo koledarčka", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "kliknite tukaj", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "za prikaz v lastnem oknu.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Datum poteka ni veljaven." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Pri shranjevanju datumov poteka in izbrisa je prišlo do napake." }, - "hideEmail": { - "message": "Skrij moj e-naslov pred prejemniki." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Nekatere nastavitve organizacije vplivajo na možnosti, ki jih imate v zvezi s pošiljkami." - }, "passwordPrompt": { "message": "Ponovno zahtevaj glavno geslo" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Napaka" }, - "regenerateUsername": { - "message": "Ponovno ustvari uporabniško ime" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Ustvari uporabniško ime" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Vrsta uporabniškega imena" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Ime spletne strani" }, - "whatWouldYouLikeToGenerate": { - "message": "Kaj želite generirati?" - }, - "passwordType": { - "message": "Vrsta gesla" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Odpre se v novem oknu" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index b1dcec00b1a..01d95a6ed1b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Копирај број лиценце" }, + "copyPrivateKey": { + "message": "Копирај приватни кључ" + }, + "copyPublicKey": { + "message": "Копирај јавни кључ" + }, + "copyFingerprint": { + "message": "Копирати отисак" + }, "copyCustomField": { "message": "Копирати $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Ауто-пуњење идентитета" }, + "fillVerificationCode": { + "message": "Пуни верификациони кôд" + }, + "fillVerificationCodeAria": { + "message": "Пуни верификациони кôд", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Генериши Лозинку (копирано)" }, @@ -438,9 +454,6 @@ "length": { "message": "Дужина" }, - "passwordMinLength": { - "message": "Минимална дужина лозинке" - }, "uppercase": { "message": "Велика слова (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Минимално специјалних знакова" }, - "avoidAmbChar": { - "message": "Избегавај двосмислене карактере", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Избегавај двосмислене карактере", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Оцени овај додатак" }, - "rateExtensionDesc": { - "message": "Молимо вас да размотрите да нам помогнете уз добру оцену!" - }, "browserNotSupportClipboard": { "message": "Ваш прегледач не подржава једноставно копирање у клипборду. Уместо тога копирајте га ручно." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Прикажи ставке идентитета на страници за лакше аутоматско допуњавање." }, + "clickToAutofillOnVault": { + "message": "Кликните на ставке за ауто-попуњавање у приказу сефа" + }, "clearClipboard": { "message": "Обриши привремену меморију", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "УПОЗОРЕЊЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Упозорење", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Потврдите извоз сефа" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Премести у организацију" }, - "share": { - "message": "Подели" - }, "movedItemToOrg": { "message": "$ITEMNAME$ премештен у $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Унесите шестоцифрени верификациони код из апликације за утврђивање аутентичности." }, + "authenticationTimeout": { + "message": "Истекло је време аутентификације" + }, + "authenticationSessionTimedOut": { + "message": "Истекло је време сесије за аутентификацију. Молим вас покрените процес пријаве поново." + }, "enterVerificationCodeEmail": { "message": "Унесите шестоцифрени верификациони код који је послан на $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Ако се открије образац за пријаву, извршите аутоматско попуњавање када се веб страница учита." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Пажња:$CLOSETAG$ Компромитоване или непоуздане веб локације могу да искористе ауто-попуњавање при учитавању странице.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Компромитоване или непоуздане веб локације могу да искористе ауто-пуњење при учитавању странице." }, @@ -1764,8 +1767,11 @@ "typeIdentity": { "message": "Идентитет" }, + "typeSshKey": { + "message": "SSH кључ" + }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Нови $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1810,7 +1816,7 @@ "message": "Колекције" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ колекција", "placeholders": { "count": { "content": "$1", @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Сигурносне белешке" }, + "sshKeys": { + "message": "SSH Кључеви" + }, "clear": { "message": "Очисти", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Клонирај" }, - "passwordGeneratorPolicyInEffect": { - "message": "Једна или више смерница организације утичу на поставке вашег генератора." - }, "passwordGenerator": { "message": "Генератор Лозинке" }, @@ -2318,6 +2324,9 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Изузети домени" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "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." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Сајт $number$ (УРЛ)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Изузете промене домена су сачуване" }, @@ -2373,14 +2394,6 @@ "message": "Детаљи Send-а", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Тражи „Send“", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Додај „Send“", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Текст" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Сакриј текст подразумевано" }, - "maxAccessCountReached": { - "message": "Достигнут максималан број приступа", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Истекло" }, - "pendingDeletion": { - "message": "Брисање на чекању" - }, "passwordProtected": { "message": "Заштићено лозинком" }, @@ -2456,24 +2462,9 @@ "message": "Уреди „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Који је ово тип „Send“-a?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Име да се опише овај „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Датотека коју желиш да пошаљеш." - }, "deletionDate": { "message": "Брисање после" }, - "deletionDateDesc": { - "message": "„Send“ ће бити трајно избрисан наведеног датума и времена.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Send ће бити трајно обрисано у наведени датум.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Рок употребе" }, - "expirationDateDesc": { - "message": "Ако је постављено, приступ овом „Send“ истиче на наведени датум и време.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 дан" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Друго" }, - "maximumAccessCount": { - "message": "Максималан број приступа" - }, - "maximumAccessCountDesc": { - "message": "Ако је постављено, корисници више неће моћи да приступе овом „Send“ када се достигне максимални број приступа.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Опционално захтевајте лозинку за приступ корисницима „Send“-у.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Додајте опционалну лозинку за примаоце да приступе овом Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Приватне белешке о овом „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Онемогућите овај „Send“ да нико не би могао да му приступи.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Након чувања, копирај УРЛ за овај „Send“ у привремену меморију.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст који желиш да пошаљеш." - }, - "sendHideText": { - "message": "Подразумевано сакриј текст за овај „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Тренутни број приступа" - }, "createSend": { "message": "Креирај нови „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2565,7 +2519,7 @@ "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": "Send ће бити доступан свакоме са везом у наредних $HOURS$ часова.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2579,7 +2533,7 @@ "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": "Send ће бити доступан свакоме са везом у наредних $DAYS$ дана.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Пре него што почнеш" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Да би користио бирање датума кроз календар", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "кликните овде", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "Да бисте приказали искачући прозор.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Наведени датум истека није исправан." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Појавила се грешка при чувању датума брисања и истека." }, - "hideEmail": { - "message": "Сакриј моју е-адресу од примаоца." - }, "hideYourEmail": { "message": "Сакријте свој имејл од гледалаца." }, - "sendOptionsPolicyInEffect": { - "message": "Једна или више смерница организације утичу на опције „Send“-а." - }, "passwordPrompt": { "message": "Поновно тражење главне лозинке" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Грешка" }, - "regenerateUsername": { - "message": "Поново генериши име" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Генериши име" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Генеришите имејл" }, - "generatorBoundariesHint": { - "message": "Вредност мора бити између $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Вредност мора бити између $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Тип имена" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Плус имејл адресе", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Име Вашег веб-сајта" }, - "whatWouldYouLikeToGenerate": { - "message": "Шта желите да генеришете?" - }, - "passwordType": { - "message": "Тип лозинке" - }, "service": { "message": "Сервис" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Поново послати обавештење" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Погледајте сав извештај у опције" + }, + "viewAllLoginOptionsV1": { "message": "Погледајте сав извештај у опције" }, "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." }, + "aNotificationWasSentToYourDevice": { + "message": "Обавештење је послато на ваш уређај" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Бићете обавештени када захтев буде одобрен" + }, + "needAnotherOptionV1": { + "message": "Треба Вам друга опције?" + }, "loginInitiated": { "message": "Пријава је покренута" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Отвара се у новом прозору" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запамтити овај уређај да би будуће пријаве биле беспрекорне" + }, "deviceApprovalRequired": { "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, + "deviceApprovalRequiredV2": { + "message": "Потребно је одобрење уређаја" + }, + "selectAnApprovalOptionBelow": { + "message": "Изаберите опцију одобрења у наставку" + }, "rememberThisDevice": { "message": "Запамти овај уређај" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Недостаје имејл корисника" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Имејл активног корисника није пронађен. Одјављивање." + }, "deviceTrusted": { "message": "Уређај поуздан" }, @@ -3401,7 +3386,7 @@ "message": "1 поље захтева вашу пажњу." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ поља захтевају вашу пажњу.", "placeholders": { "count": { "content": "$1", @@ -3521,6 +3506,14 @@ "message": "Откључајте свој налог, отвара се у новом прозору", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Једнократни верификациони кôд заснован на времену", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Преостало време до истека актуелног ТОТП-а", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Попунити акредитиве за", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Приступ" }, + "loggedInExclamation": { + "message": "Пријављено!" + }, "passkeyNotCopied": { "message": "Приступни кључ неће бити копиран" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Филтери" }, + "filterVault": { + "message": "Филтер сефа" + }, + "filterApplied": { + "message": "Примењен је један филтер" + }, + "filterAppliedPlural": { + "message": "Примењени су $COUNT$ филтера", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Личне информације" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Прикажи број предлога за ауто-попуњавање пријаве на икони додатка" }, + "showQuickCopyActions": { + "message": "Приказати брзе радње копирања у Сефу" + }, "systemDefault": { "message": "Системски подразумевано" }, "enterprisePolicyRequirementsApplied": { "message": "Захтеви политике предузећа су примењени на ово подешавање" }, + "sshPrivateKey": { + "message": "Приватни кључ" + }, + "sshPublicKey": { + "message": "Јавни кључ" + }, + "sshFingerprint": { + "message": "Отисак прста" + }, + "sshKeyAlgorithm": { + "message": "Врста кључа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-бита" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-бита" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-бита" + }, "retry": { "message": "Пробај поново" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Немате дозволу да уређујете ову ставку" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Аутентификација" }, @@ -4712,23 +4777,23 @@ "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": { @@ -4736,56 +4801,107 @@ "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": "Компактни режим" + }, + "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": "Ширина додатка" + }, + "wide": { + "message": "Широко" + }, + "extraWide": { + "message": "Врло широко" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index e58a262d59c..a443a8e6b2e 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -150,7 +150,16 @@ "message": "Kopiera passnummer" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopiera licensnummer" + }, + "copyPrivateKey": { + "message": "Kopiera privat nyckel" + }, + "copyPublicKey": { + "message": "Kopiera offentlig nyckel" + }, + "copyFingerprint": { + "message": "Kopiera fingeravtryck" }, "copyCustomField": { "message": "Kopiera $FIELD$", @@ -168,7 +177,7 @@ "message": "Kopiera anteckningar" }, "fill": { - "message": "Fill", + "message": "Fyll", "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": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofyll identitet" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Skapa lösenord (kopierad)" }, @@ -366,7 +382,7 @@ "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Inga mappar tillagda" }, "createFoldersToOrganize": { "message": "Skapa mappar för att organisera dina valvobjekt" @@ -438,9 +454,6 @@ "length": { "message": "Längd" }, - "passwordMinLength": { - "message": "Minsta tillåtna lösenordslängd" - }, "uppercase": { "message": "Versaler (A-Ö)", "description": "deprecated. Use uppercaseLabel instead." @@ -458,11 +471,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "include": { - "message": "Include", + "message": "Inkludera", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkludera versaler", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -470,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkludera gemener", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -478,7 +491,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inkludera siffror", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -486,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkludera specialtecken", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -512,12 +525,8 @@ "minSpecial": { "message": "Minsta antal speciella tecken" }, - "avoidAmbChar": { - "message": "Undvik tvetydiga tecken", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Undvik tvetydiga tecken", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -549,7 +558,7 @@ "message": "Authenticator secret" }, "passphrase": { - "message": "Lösenordsfras" + "message": "Lösenfras" }, "favorite": { "message": "Favorit" @@ -632,9 +641,6 @@ "rateExtension": { "message": "Betygsätt tillägget" }, - "rateExtensionDesc": { - "message": "Överväg gärna att skriva en recension om oss!" - }, "browserNotSupportClipboard": { "message": "Din webbläsare har inte stöd för att enkelt kopiera till urklipp. Kopiera till urklipp manuellt istället." }, @@ -828,7 +834,7 @@ "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." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Läs mer om autentiserare" }, "copyTOTP": { "message": "Kopiera autentiseringsnyckel (TOTP)" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Lista identitetsobjekt på fliksidan för enkel automatisk fyllning." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Rensa urklipp", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "VARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Bekräfta export av valv" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Flytta till organisation" }, - "share": { - "message": "Dela" - }, "movedItemToOrg": { "message": "$ITEMNAME$ flyttades till $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Ange den 6-siffriga verifieringskoden från din autentiseringsapp." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Ange den 6-siffriga verifieringskoden som skickades till $EMAIL$.", "placeholders": { @@ -1397,7 +1413,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Ange en kod som skickar per e-post." }, "selfHostedEnvironment": { "message": "Egen-hostad miljö" @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Utför automatisk ifyllnad om ett inloggningsformulär upptäcks när webbsidan laddas." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Komprometterade eller ej betrodda webbplatser kan utnyttja automatisk ifyllnad vid sidladdning." }, @@ -1514,7 +1517,7 @@ "message": "Läs mer om risker" }, "learnMoreAboutAutofill": { - "message": "Läs mer om automatisk ifyllnad" + "message": "Läs mer om autofyll" }, "defaultAutoFillOnPageLoad": { "message": "Standardinställning för autofyll för inloggningsobjekt" @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identitet" }, + "typeSshKey": { + "message": "SSH-nyckel" + }, "newItemHeader": { "message": "Ny $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Säkra anteckningar" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Rensa", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Klona" }, - "passwordGeneratorPolicyInEffect": { - "message": "En eller flera organisationspolicyer påverkar dina generatorinställningar." - }, "passwordGenerator": { "message": "Lösenordsgenerator" }, @@ -2189,7 +2195,7 @@ "message": "Unsubscribe" }, "atAnyTime": { - "message": "at any time." + "message": "när som helst." }, "byContinuingYouAgreeToThe": { "message": "By continuing, you agree to the" @@ -2318,6 +2324,9 @@ "message": "Domäner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Exkluderade domäner" }, @@ -2327,8 +2336,17 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "Webbplats $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Sök bland Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Lägg till Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Det maximala antalet åtkomster har uppnåtts", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Utgången" }, - "pendingDeletion": { - "message": "Väntar på radering" - }, "passwordProtected": { "message": "Lösenordsskyddad" }, @@ -2456,24 +2462,9 @@ "message": "Redigera Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Vilken typ av Send är detta?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Ett vänligt namn som beskriver denna Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Filen du vill skicka." - }, "deletionDate": { "message": "Raderingsdatum" }, - "deletionDateDesc": { - "message": "Denna Send kommer att raderas permanent på angivet datum och klockslag.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Utgångsdatum" }, - "expirationDateDesc": { - "message": "Om angiven kommer åtkomst till denna Send att upphöra på angivet datum och tid.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 dag" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Anpassad" }, - "maximumAccessCount": { - "message": "Maximalt antal åtkomster" - }, - "maximumAccessCountDesc": { - "message": "Om angivet kommer användare inte längre kunna komma åt denna Send när det maximala antalet åtkomster har uppnåtts.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Kräv valfritt ett lösenord för att användare ska komma åt denna Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Privata anteckningar om denna Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Inaktivera denna Send så att ingen kan komma åt den.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kopiera länken till denna Send vid sparande.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Texten du vill skicka." - }, - "sendHideText": { - "message": "Dölj texten för denna Send som standard", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Nuvarande antal åtkomster" - }, "createSend": { "message": "Ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Innan du börjar" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "För att använda en datumväljare med kalenderstil", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "klicka här", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "för att öppna ett nytt fönster.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Det angivna utgångsdatumet är inte giltigt." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Det gick inte att spara raderings- och utgångsdatum." }, - "hideEmail": { - "message": "Dölj min e-postadress för mottagare." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "En eller flera organisationsriktlinjer påverkar dina Send-inställningar." - }, "passwordPrompt": { "message": "Återupprepa huvudlösenord" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Fel" }, - "regenerateUsername": { - "message": "Återskapa användarnamn" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generera användarnamn" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Värde måste vara mellan $MIN$ och $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Typ av användarnamn" + "passwordLengthRecommendationHint": { + "message": " Använd minst $RECOMMENDED$ tecken för att generera ett starkt lösenord.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Använd minst $RECOMMENDED$ ord för att generera en stark lösenfras.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plusadresserad e-post", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Webbplatsnamn" }, - "whatWouldYouLikeToGenerate": { - "message": "Vad skulle du vilja generera?" - }, - "passwordType": { - "message": "Typ av lösenord" - }, "service": { "message": "Tjänst" }, @@ -2932,7 +2890,7 @@ "message": "Skapa ett e-postalias med en extern vidarebefordranstjänst." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomän", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Skicka avisering igen" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { "message": "Visa alla inloggningsalternativ" }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" + }, "notificationSentDevice": { "message": "En avisering har skickats till din enhet." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Inloggning påbörjad" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Öppnas i ett nytt fönster" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Kom ihåg denna enhet" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Användarens e-postadress saknas" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Enhet betrodd" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fyll i uppgifter för", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Lösennyckeln kommer inte kopieras" }, @@ -3846,7 +3842,7 @@ "message": "Godkänn inloggningsbegäran i din autentiseringsapp eller ange en engångskod." }, "passcode": { - "message": "Passcode" + "message": "Lösenkod" }, "lastPassMasterPassword": { "message": "LastPass Huvudlösenord" @@ -4037,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "Kopiera anteckning - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Visa objekt - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4093,7 +4089,7 @@ "message": "Assign to collections" }, "copyEmail": { - "message": "Copy email" + "message": "Kopiera e-postadress" }, "copyPhone": { "message": "Kopiera telefon" @@ -4211,7 +4207,7 @@ "message": "Maximal filstorlek är 500 MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "Radera bilaga $NAME$", "placeholders": { "name": { "content": "$1", @@ -4240,11 +4236,26 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filtrera valv" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, "identification": { - "message": "Identification" + "message": "Identifikation" }, "contactInfo": { "message": "Contact info" @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "Systemstandard" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Privat nyckel" + }, + "sshPublicKey": { + "message": "Offentlig nyckel" + }, + "sshFingerprint": { + "message": "Fingeravtryck" + }, + "sshKeyAlgorithm": { + "message": "Nyckeltyp" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Försök igen" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4660,7 +4725,7 @@ "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Utropstecken", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { @@ -4744,7 +4809,7 @@ "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semikolon", + "message": "Semicolon", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { @@ -4756,11 +4821,11 @@ "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Mindre än", + "message": "Less than", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Större än", + "message": "Greater than", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { @@ -4780,12 +4845,63 @@ "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Gemen" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Versal" }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index b8075804229..e34751eea7d 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "List identity items on the Tab page for easy autofill." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Confirm vault export" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Identity" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 9f111f9bec7..1b493de3d2c 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Autofill identity" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Generate Password (copied)" }, @@ -438,9 +454,6 @@ "length": { "message": "ความยาว" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "ตัวพิมพ์ใหญ่ (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Minimum Special" }, - "avoidAmbChar": { - "message": "Avoid Ambiguous Characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Rate the Extension" }, - "rateExtensionDesc": { - "message": "โปรดพิจารณา ช่วยเราด้วยการตรวจสอบที่ดี!" - }, "browserNotSupportClipboard": { "message": "เว็บเบราว์เซอร์ของคุณไม่รองรับการคัดลอกคลิปบอร์ดอย่างง่าย คัดลอกด้วยตนเองแทน" }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "แสดงรายการข้อมูลประจำตัวในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "ล้างคลิปบอร์ด", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "คำเตือน", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "ยืนยันการส่งออกตู้นิรภัย" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "ย้ายไปยังแบบองค์กร" }, - "share": { - "message": "แชร์" - }, "movedItemToOrg": { "message": "ย้าย $ITEMNAME$ ไปยัง $ORGNAME$ แล้ว", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Enter the 6 digit verification code from your authenticator app." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, autofill when the web page loads." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Compromised or untrusted websites can exploit autofill on page load." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "ข้อมูลระบุตัวตน" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Secure Notes" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "ลบทิ้ง", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "โคลน" }, - "passwordGeneratorPolicyInEffect": { - "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายส่งผลต่อการตั้งค่าตัวสร้างของคุณ" - }, "passwordGenerator": { "message": "Password generator" }, @@ -2318,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2327,6 +2336,15 @@ "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." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "ค้นหาใน Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "เพิ่ม Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "ข้อความ" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2456,24 +2462,9 @@ "message": "แก้ไข Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 วัน" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "ข้อความที่คุณต้องการส่ง" - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Generate username" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Username type" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Resend notification" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { "message": "View all log in options" }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Login initiated" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Opens in a new window" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Fill credentials for", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Passkey will not be copied" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Filters" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Personal details" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index e49ada7538e..8095a9f6045 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -120,7 +120,7 @@ "message": "Parolayı kopyala" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Parolayı kopyala" }, "copyNote": { "message": "Notu kopyala" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Ruhsat numarasını kopyala" }, + "copyPrivateKey": { + "message": "Özel anahtarı kopyala" + }, + "copyPublicKey": { + "message": "Ortak anahtarı kopyala" + }, + "copyFingerprint": { + "message": "Parmak izini kopyala" + }, "copyCustomField": { "message": "$FIELD$ alanını kopyala", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Kimliği otomatik doldur" }, + "fillVerificationCode": { + "message": "Doğrulama kodunu doldur" + }, + "fillVerificationCodeAria": { + "message": "Doğrulama kodunu doldur", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Parola oluştur (ve kopyala)" }, @@ -318,7 +334,7 @@ "message": "Bitwarden Authenticator" }, "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 Authenticator, kimlik doğrulama anahtarlarınızı saklama ve 2 aşamalı doğrulama akışları için TOTP anahtarı üretme imkânı sağlar. Bilgi almak için bitwarden.com sitesini ziyaret edin" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" @@ -405,10 +421,10 @@ "message": "Son eşitleme:" }, "passGen": { - "message": "Parola üretici" + "message": "Parola üreteci" }, "generator": { - "message": "Oluşturucu", + "message": "Üreteç", "description": "Short for 'credential generator'." }, "passGenInfo": { @@ -427,7 +443,7 @@ "message": "Parola oluştur" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Parola üret" }, "regeneratePassword": { "message": "Yeni parola oluştur" @@ -438,9 +454,6 @@ "length": { "message": "Uzunluk" }, - "passwordMinLength": { - "message": "Minimum parola uzunluğu" - }, "uppercase": { "message": "Büyük harf (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "En az özel karakter" }, - "avoidAmbChar": { - "message": "Okurken karışabilecek karakterleri kullanma", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Okurken karışabilecek karakterleri kullanma", "description": "Label for the avoid ambiguous characters checkbox." @@ -591,7 +600,7 @@ "message": "Web sitesini aç" }, "launchWebsiteName": { - "message": "$ITEMNAME$ web sitesini aç", + "message": "$ITEMNAME$ sitesini aç", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "Uzantıyı değerlendirin" }, - "rateExtensionDesc": { - "message": "İyi bir yorum yazarak bizi destekleyebilirsiniz." - }, "browserNotSupportClipboard": { "message": "Web tarayıcınız panoya kopyalamayı desteklemiyor. Parolayı elle kopyalayın." }, @@ -828,7 +834,7 @@ "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." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Kimlik doğrulayıcılar hakkında bilgi alın" }, "copyTOTP": { "message": "Kimlik doğrulama anahtarını kopyala (TOTP)" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Kolay otomatik doldurma için sekme sayfasında kimlikleri listele." }, + "clickToAutofillOnVault": { + "message": "Kasa görünümünde otomatik doldurmak istediğiniz kayıtlara tıklayın" + }, "clearClipboard": { "message": "Panoyu temizle", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "UYARI", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Uyarı", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Kasayı dışa aktarmayı onaylayın" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Kuruluşa taşı" }, - "share": { - "message": "Paylaş" - }, "movedItemToOrg": { "message": "$ITEMNAME$ $ORGNAME$ kuruluşuna taşındı", "placeholders": { @@ -1256,7 +1266,7 @@ "message": "Premium üyeliği bitwarden.com web kasası üzerinden satın alabilirsiniz. Şimdi siteye gitmek ister misiniz?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Bitwarden web uygulamasındaki hesap ayarlarınızdan Premium abonelik satın alabilirsiniz." }, "premiumCurrentMember": { "message": "Premium üyesiniz!" @@ -1265,7 +1275,7 @@ "message": "Bitwarden'ı desteklediğiniz için teşekkür ederiz." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "Premium'a yükseltin ve şunları alın:" }, "premiumPrice": { "message": "Bunların hepsi sadece yılda $PRICE$!", @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Kimlik doğrulama uygulamanızdaki 6 haneli doğrulama kodunu girin." }, + "authenticationTimeout": { + "message": "Kimlik doğrulama zaman aşımı" + }, + "authenticationSessionTimedOut": { + "message": "Kimlik doğrulama oturumu zaman aşımına uğradı. Lütfen giriş sürecini yeniden başlatın." + }, "enterVerificationCodeEmail": { "message": "$EMAIL$ adresine e-postayla gönderdiğimiz 6 haneli doğrulama kodunu girin.", "placeholders": { @@ -1409,7 +1425,7 @@ "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "İleri düzey yapılandırma için her hizmetin taban URL'sini bağımsız olarak belirleyebilirsiniz." }, "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Sayfa yüklendiğinde giriş formu tespit edilirse otomatik olarak formu doldur." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Uyarı:$CLOSETAG$ Ele geçirilmiş veya güvenilmeyen web siteleri sayfa yüklenirken otomatik doldurmayı suistimal edebilir.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Ele geçirilmiş veya güvenilmeyen web siteleri sayfa yüklenirken otomatik doldurmayı suistimal edebilir." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Kimlik" }, + "typeSshKey": { + "message": "SSH anahtarı" + }, "newItemHeader": { "message": "Yeni $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Güvenli notlar" }, + "sshKeys": { + "message": "SSH anahtarları" + }, "clear": { "message": "Temizle", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Klonla" }, - "passwordGeneratorPolicyInEffect": { - "message": "Bir ya da daha fazla kuruluş ilkesi, oluşturucu ayarlarınızı etkiliyor." - }, "passwordGenerator": { "message": "Parola üreteci" }, @@ -2216,10 +2222,10 @@ "message": "Tamam" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Erişim Anahtarı Yenileme Hatası" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Yenileme veya API anahtarı bulunamadı. Lütfen çıkış yapıp tekrar giriş yapmayı deneyin." }, "desktopSyncVerificationTitle": { "message": "Masaüstü eşitleme doğrulaması" @@ -2318,6 +2324,9 @@ "message": "Alan adları", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Engellenen alan adları" + }, "excludedDomains": { "message": "Hariç tutulan alan adları" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, oturum açmış tüm hesaplar için bu alan adlarının hesap bilgilerini kaydetmeyi sormayacaktır. Değişikliklerin etkili olması için sayfayı yenilemeniz gerekir." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web sitesi $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Engelli alan adı değişiklikleri kaydedildi" + }, "excludedDomainsSavedSuccess": { "message": "Alan adı istisnası değişiklikleri kaydedildi" }, @@ -2373,14 +2394,6 @@ "message": "Send ayrıntıları", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Send'lerde ara", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Send ekle", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Metin" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Metni varsayılan olarak gizle" }, - "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." - }, "expired": { "message": "Süresi dolmuş" }, - "pendingDeletion": { - "message": "Silinmesi bekleniyor" - }, "passwordProtected": { "message": "Parola korumalı" }, @@ -2456,24 +2462,9 @@ "message": "Send'i düzenle", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Bu ne tür bir Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Bu Send'i açıklayan anlaşılır bir ad", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Göndermek istediğiniz dosya." - }, "deletionDate": { "message": "Silinme tarihi" }, - "deletionDateDesc": { - "message": "Bu Send belirtilen tarih ve saatte kalıcı olacak silinecek.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Bu Send belirtilen tarihte kalıcı olacak silinecek.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Son kullanma tarihi" }, - "expirationDateDesc": { - "message": "Bunu ayarlarsanız belirtilen tarih ve saatten sonra bu Send'e erişilemeyecektir.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 gün" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Özel" }, - "maximumAccessCount": { - "message": "Maksimum erişim sayısı" - }, - "maximumAccessCountDesc": { - "message": "Bunu ayarlarsanız maksimum erişim sayısına ulaşıldıktan sonra bu Send'e erişilemeyecektir.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Kullanıcıların bu Send'e erişmek için parola girmelerini isteyebilirsiniz.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Alıcıların bu Send'e erişmesi için isterseniz parola ekleyebilirsiniz.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Bu Send ile ilgili özel notlar.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Kimsenin erişememesi için bu Send'i devre dışı bırak.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Kaydettikten sonra bu Send'in linkini panoya kopyala.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Göndermek istediğiniz metin." - }, - "sendHideText": { - "message": "Bu Send'in metnini varsayılan olarak gizle.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Mevcut erişim sayısı" - }, "createSend": { "message": "Yeni Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Başlamadan önce" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Takvim tarzı tarih seçiyi kullanmak için", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "buraya tıklayarak", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "yeni bir pencere açın.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Belirtilen son kullanma tarihi geçersiz." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Silinme ve son kullanma tarihleriniz kaydedilirken bir hata oluştu." }, - "hideEmail": { - "message": "E-posta adresimi alıcılardan gizle." - }, "hideYourEmail": { "message": "E-posta adresimi Send'i görüntüleyenlerden gizle." }, - "sendOptionsPolicyInEffect": { - "message": "Bir veya daha fazla kuruluş ilkesi Send seçeneklerinizi etkiliyor." - }, "passwordPrompt": { "message": "Ana parolayı yeniden iste" }, @@ -2868,17 +2804,28 @@ "error": { "message": "Hata" }, - "regenerateUsername": { - "message": "Kullanıcı adını yeniden oluştur" + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Kullanıcı adı oluştur" }, "generateEmail": { - "message": "E-posta oluştur" + "message": "E-posta üret" }, - "generatorBoundariesHint": { - "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır", + "spinboxBoundariesHint": { + "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Kullanıcı adı türü" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Artı adresli e-posta", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Web sitesi adı" }, - "whatWouldYouLikeToGenerate": { - "message": "Ne oluşturmak istersiniz?" - }, - "passwordType": { - "message": "Parola türü" - }, "service": { "message": "Servis" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Bildirimi yeniden gönder" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Tüm giriş seçeneklerini gör" + }, + "viewAllLoginOptionsV1": { "message": "Tüm giriş seçeneklerini gör" }, "notificationSentDevice": { "message": "Cihazınıza bir bildirim gönderildi." }, + "aNotificationWasSentToYourDevice": { + "message": "Cihazınıza bir bildirim gönderildi" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lütfen hesabınızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "İsteğiniz onaylanınca size haber vereceğiz" + }, + "needAnotherOptionV1": { + "message": "Başka bir seçeneğe mi ihtiyacınız var?" + }, "loginInitiated": { "message": "Giriş başlatıldı" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Yeni pencerede açılır" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Sonraki girişleri kolaylaştırmak için bu cihazı hatırla" + }, "deviceApprovalRequired": { "message": "Cihaz onayı gerekiyor. Lütfen onay yönteminizi seçin:" }, + "deviceApprovalRequiredV2": { + "message": "Cihazı onaylamanız gerekiyor" + }, + "selectAnApprovalOptionBelow": { + "message": "Aşağıdan bir onay yöntemi seçin" + }, "rememberThisDevice": { "message": "Bu cihazı hatırla" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Kullanıcının e-postası eksik" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktif kullanıcı e-postası bulunamadı. Çıkış yapılıyor." + }, "deviceTrusted": { "message": "Cihaza güvenildi" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Zamana dayalı tek seferlik parola doğrulama kodu", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Geçerli TOTP için kalan süre", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Bilgileri doldur", "description": "Screen reader text for when overlay item is in focused" @@ -3747,7 +3740,10 @@ "message": "Geçiş anahtarı" }, "accessing": { - "message": "Accessing" + "message": "Erişim" + }, + "loggedInExclamation": { + "message": "Giriş yapıldı!" }, "passkeyNotCopied": { "message": "Geçiş anahtarı kopyalanmayacak" @@ -3774,7 +3770,7 @@ "message": "Bu siteyle eşleşen hesap bulunamadı" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Geçiş anahtarı ara veya yeni hesap olarak kaydet" }, "confirm": { "message": "Onayla" @@ -4187,7 +4183,7 @@ "message": "Ek bilgiler" }, "itemHistory": { - "message": "Öğe geçmişi" + "message": "Kayıt geçmişi" }, "lastEdited": { "message": "Son düzenlenme" @@ -4240,6 +4236,21 @@ "filters": { "message": "Filtreler" }, + "filterVault": { + "message": "Kasayı filtrele" + }, + "filterApplied": { + "message": "1 filtre uygulandı" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filtre uygulandı", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Kişisel bilgiler" }, @@ -4514,7 +4525,7 @@ "message": "Successfully assigned collections" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "Hiçbir şey seçmediniz." }, "movedItemsToOrg": { "message": "Selected items moved to $ORGNAME$", @@ -4526,7 +4537,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "Kayıtlar $ORGNAME$ kuruluşuna taşındı", "placeholders": { "orgname": { "content": "$1", @@ -4535,7 +4546,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "Kayıt $ORGNAME$ kuruluşuna taşındı", "placeholders": { "orgname": { "content": "$1", @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Otomatik öneri sayısını uzantı simgesinde göster" }, + "showQuickCopyActions": { + "message": "Kasada hızlı kopyalama komutlarını göster" + }, "systemDefault": { "message": "Sistem varsayılanı" }, "enterprisePolicyRequirementsApplied": { "message": "Bu ayara kurumsal ilke gereksinimleri uygulandı" }, + "sshPrivateKey": { + "message": "Özel anahtar" + }, + "sshPublicKey": { + "message": "Ortak anahtar" + }, + "sshFingerprint": { + "message": "Parmak izi" + }, + "sshKeyAlgorithm": { + "message": "Anahtar türü" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 bit" + }, "retry": { "message": "Yeniden dene" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Üretilen parola" + }, + "compactMode": { + "message": "Kompakt mod" + }, + "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" + }, + "wide": { + "message": "Geniş" + }, + "extraWide": { + "message": "Ekstra geniş" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 01cdc4cab9e..d6b0b88ead2 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -26,7 +26,7 @@ "message": "Увійти з ключем доступу" }, "useSingleSignOn": { - "message": "Використовувати єдиний вхід" + "message": "Використати єдиний вхід" }, "welcomeBack": { "message": "З поверненням" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Копіювати номер ліцензії" }, + "copyPrivateKey": { + "message": "Копіювати закритий ключ" + }, + "copyPublicKey": { + "message": "Копіювати відкритий ключ" + }, + "copyFingerprint": { + "message": "Копіювати відбиток" + }, "copyCustomField": { "message": "Копіювати $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Автозаповнення посвідчень" }, + "fillVerificationCode": { + "message": "Заповнити код підтвердження" + }, + "fillVerificationCodeAria": { + "message": "Заповнити код підтвердження", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Генерувати пароль (з копіюванням)" }, @@ -438,9 +454,6 @@ "length": { "message": "Довжина" }, - "passwordMinLength": { - "message": "Мінімальна довжина пароля" - }, "uppercase": { "message": "Верхній регістр (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Мінімум спеціальних символів" }, - "avoidAmbChar": { - "message": "Уникати неоднозначних символів", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Уникати неоднозначних символів", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Оцінити розширення" }, - "rateExtensionDesc": { - "message": "Розкажіть іншим про свої враження, залишивши хороший відгук!" - }, "browserNotSupportClipboard": { "message": "Ваш браузер не підтримує копіювання даних в буфер обміну. Скопіюйте вручну." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Показувати список посвідчень на сторінці вкладки для легкого автозаповнення." }, + "clickToAutofillOnVault": { + "message": "Натисніть на запис у режимі перегляду сховища для автозаповнення" + }, "clearClipboard": { "message": "Очистити буфер обміну", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "ПОПЕРЕДЖЕННЯ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Попередження", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Підтвердити експорт сховища" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Перемістити до організації" }, - "share": { - "message": "Поділитися" - }, "movedItemToOrg": { "message": "$ITEMNAME$ переміщено до $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Введіть 6-значний код підтвердження з програми автентифікації." }, + "authenticationTimeout": { + "message": "Час очікування автентифікації" + }, + "authenticationSessionTimedOut": { + "message": "Час очікування сеансу автентифікації завершився. Перезапустіть процес входу в систему." + }, "enterVerificationCodeEmail": { "message": "Введіть 6-значний код підтвердження, надісланий на $EMAIL$.", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Якщо виявлено форму входу, автоматично заповнювати її під час завантаження вебсторінки." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Обережно!$CLOSETAG$ Скомпрометовані або ненадійні вебсайти можуть використати функцію автозаповнення під час завантаження сторінки для завдання шкоди.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Скомпрометовані або ненадійні вебсайти можуть використати функцію автозаповнення під час завантаження сторінки для завдання шкоди." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Посвідчення" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "newItemHeader": { "message": "Новий $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Захищені нотатки" }, + "sshKeys": { + "message": "Ключі SSH" + }, "clear": { "message": "Стерти", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Клонувати" }, - "passwordGeneratorPolicyInEffect": { - "message": "На параметри генератора впливають одна чи декілька політик організації." - }, "passwordGenerator": { "message": "Генератор паролів" }, @@ -2318,6 +2324,9 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблоковані домени" + }, "excludedDomains": { "message": "Виключені домени" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не запитуватиме про збереження даних входу для цих доменів для всіх облікових записів, до яких виконано вхід. Потрібно оновити сторінку для застосування змін." }, + "blockedDomainsDesc": { + "message": "Автозаповнення та інші пов'язані функції не пропонуватимуться для цих вебсайтів. Вам слід оновити сторінку для застосування змін." + }, + "autofillBlockedNotice": { + "message": "Автозаповнення заблоковано для цього вебсайту. Перегляньте або змініть це в налаштуваннях." + }, + "autofillBlockedTooltip": { + "message": "Автозаповнення заблоковано на цьому вебсайті. Перевірте налаштування." + }, "websiteItemLabel": { "message": "Вебсайт $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Зміни заблокованих доменів збережено" + }, "excludedDomainsSavedSuccess": { "message": "Виняток для домену збережено" }, @@ -2373,14 +2394,6 @@ "message": "Надіслати подробиці", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Шукати відправлення", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Додати відправлення", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Текст" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Типово приховувати текст" }, - "maxAccessCountReached": { - "message": "Досягнуто максимальної кількості доступів", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Термін дії завершився" }, - "pendingDeletion": { - "message": "Очікується видалення" - }, "passwordProtected": { "message": "Захищено паролем" }, @@ -2456,24 +2462,9 @@ "message": "Редагування", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Який це тип відправлення?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Опис цього відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Файл, який ви хочете відправити." - }, "deletionDate": { "message": "Термін дії" }, - "deletionDateDesc": { - "message": "Відправлення буде остаточно видалено у вказаний час.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "Відправлення буде остаточно видалено у вказану дату.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Дата завершення" }, - "expirationDateDesc": { - "message": "Якщо встановлено, термін дії цього відправлення завершиться у вказаний час.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 день" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Власний" }, - "maximumAccessCount": { - "message": "Максимальна кількість доступів" - }, - "maximumAccessCountDesc": { - "message": "Якщо встановлено, користувачі більше не зможуть отримати доступ до цього відправлення після досягнення максимальної кількості доступів.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Ви можете встановити пароль для доступу до цього відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "За бажання додайте пароль для отримувачів цього відправлення.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Особисті нотатки про це відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Деактивувати це відправлення для скасування доступу до нього.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Копіювати посилання цього відправлення перед збереженням.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст, який ви хочете відправити." - }, - "sendHideText": { - "message": "Приховувати текст цього відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Поточна кількість доступів" - }, "createSend": { "message": "Нове відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Перед початком" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Щоб використовувати календарний стиль вибору дати", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "натисніть тут", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "щоб відкріпити ваше вікно.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Вказано недійсний термін дії." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "При збереженні дат видалення і терміну дії виникла помилка." }, - "hideEmail": { - "message": "Приховувати мою адресу електронної пошти від отримувачів." - }, "hideYourEmail": { "message": "Приховати адресу е-пошти від отримувачів." }, - "sendOptionsPolicyInEffect": { - "message": "На параметри відправлень впливають одна чи декілька політик організації." - }, "passwordPrompt": { "message": "Повторний запит головного пароля" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Помилка" }, - "regenerateUsername": { - "message": "Повторно генерувати ім'я користувача" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Генерувати ім'я користувача" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Генерувати е-пошту" }, - "generatorBoundariesHint": { - "message": "Значення має бути між $MIN$ та $MAX$", + "spinboxBoundariesHint": { + "message": "Значення має бути між $MIN$ та $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Тип імені користувача" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Адреса е-пошти з плюсом", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Назва вебсайту" }, - "whatWouldYouLikeToGenerate": { - "message": "Що ви бажаєте згенерувати?" - }, - "passwordType": { - "message": "Тип пароля" - }, "service": { "message": "Послуга" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Надіслати сповіщення ще раз" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "Переглянути всі варіанти входу" + }, + "viewAllLoginOptionsV1": { "message": "Переглянути всі варіанти входу" }, "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." }, + "aNotificationWasSentToYourDevice": { + "message": "Сповіщення надіслано на ваш пристрій" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Переконайтеся, що ваш обліковий запис розблоковано і фраза відбитка на іншому пристрої збігається" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Після схвалення запиту ви отримаєте сповіщення" + }, + "needAnotherOptionV1": { + "message": "Потрібен інший варіант?" + }, "loginInitiated": { "message": "Ініційовано вхід" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Відкривається у новому вікні" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запам'ятайте цей пристрій, щоб спростити майбутні входи в систему" + }, "deviceApprovalRequired": { "message": "Необхідне підтвердження пристрою. Виберіть варіант підтвердження нижче:" }, + "deviceApprovalRequiredV2": { + "message": "Потрібне підтвердження пристрою" + }, + "selectAnApprovalOptionBelow": { + "message": "Виберіть варіант підтвердження нижче" + }, "rememberThisDevice": { "message": "Запам'ятати цей пристрій" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Немає адреси електронної пошти" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Адресу е-пошти активного користувача не знайдено. Виконується вихід із системи." + }, "deviceTrusted": { "message": "Довірений пристрій" }, @@ -3521,6 +3506,14 @@ "message": "Розблокування облікового запису – відкриється нове вікно", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Код підтвердження одноразового пароля", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Час, що залишився до завершення чинного TOTP", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Заповнити облікові дані для", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Доступ" }, + "loggedInExclamation": { + "message": "Ви увійшли!" + }, "passkeyNotCopied": { "message": "Ключ доступу не буде скопійовано" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Фільтри" }, + "filterVault": { + "message": "Фільтр сховища" + }, + "filterApplied": { + "message": "Застосовано один фільтр" + }, + "filterAppliedPlural": { + "message": "Застосовані фільтри: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Особисті дані" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Показувати кількість пропозицій автозаповнення на піктограмі розширення" }, + "showQuickCopyActions": { + "message": "Показати дії швидкого копіювання у сховищі" + }, "systemDefault": { "message": "Типово (система)" }, "enterprisePolicyRequirementsApplied": { "message": "До цього налаштування застосовано вимоги політики компанії" }, + "sshPrivateKey": { + "message": "Закритий ключ" + }, + "sshPublicKey": { + "message": "Відкритий ключ" + }, + "sshFingerprint": { + "message": "Цифровий відбиток" + }, + "sshKeyAlgorithm": { + "message": "Тип ключа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Повторити спробу" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "Вам не дозволено редагувати цей запис" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Аутентифікація" }, @@ -4656,7 +4721,7 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Зворотна лапка", + "message": "Зворотний апостроф", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Згенерований пароль" + }, + "compactMode": { + "message": "Компактний режим" + }, + "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": "Ширина вікна розширення" + }, + "wide": { + "message": "Широке" + }, + "extraWide": { + "message": "Дуже широке" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 61e211063de..4ccdaf808f3 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "Sao chép số giấy phép" }, + "copyPrivateKey": { + "message": "Copy private key" + }, + "copyPublicKey": { + "message": "Copy public key" + }, + "copyFingerprint": { + "message": "Copy fingerprint" + }, "copyCustomField": { "message": "Copy $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "Tự động điền danh tính" }, + "fillVerificationCode": { + "message": "Fill verification code" + }, + "fillVerificationCodeAria": { + "message": "Fill Verification Code", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "Tạo mật khẩu (đã sao chép)" }, @@ -438,9 +454,6 @@ "length": { "message": "Độ dài" }, - "passwordMinLength": { - "message": "Độ dài mật khẩu tối thiểu" - }, "uppercase": { "message": "Chữ in hoa (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "Số kí tự đặc biệt tối thiểu" }, - "avoidAmbChar": { - "message": "Tránh các ký tự không rõ ràng", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -632,9 +641,6 @@ "rateExtension": { "message": "Đánh giá tiện ích mở rộng" }, - "rateExtensionDesc": { - "message": "Xin hãy nhìn nhận và đánh giá tốt cho chúng tôi!" - }, "browserNotSupportClipboard": { "message": "Trình duyệt web của bạn không hỗ trợ dễ dàng sao chép bộ nhớ tạm. Bạn có thể sao chép nó theo cách thủ công để thay thế." }, @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "Liệt kê các mục danh tính trên trang Tab để dễ dàng tự động điền." }, + "clickToAutofillOnVault": { + "message": "Click items to autofill on Vault view" + }, "clearClipboard": { "message": "Dọn dẹp khay nhớ tạm", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1116,6 +1125,10 @@ "message": "CẢNH BÁO", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "Warning", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "Xác nhận xuất kho" }, @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "Di chuyển đến tổ chức" }, - "share": { - "message": "Chia sẻ" - }, "movedItemToOrg": { "message": "$ITEMNAME$ đã được di chuyển đến $ORGNAME$", "placeholders": { @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "Nhập mã xác nhận 6 chữ số từ ứng dụng xác thực của bạn." }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "enterVerificationCodeEmail": { "message": "Nhập mã xác nhận 6 chữ số đã được gửi tới email", "placeholders": { @@ -1494,19 +1510,6 @@ "enableAutoFillOnPageLoadDesc": { "message": "Nếu phát hiện biểu mẫu đăng nhập, thực hiện tự động điền khi trang web tải xong." }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Cảnh báo:$CLOSETAG$ Các trang web bị xâm phạm hoặc không đáng tin cậy có thể lợi dụng tính năng tự động điền khi trang web được tải.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "Các trang web bị xâm phạm hoặc không đáng tin cậy có thể khai thác tính năng tự động điền khi tải trang." }, @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "Danh tính" }, + "typeSshKey": { + "message": "SSH key" + }, "newItemHeader": { "message": "$TYPE$ mới", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "Ghi chú bảo mật" }, + "sshKeys": { + "message": "SSH Keys" + }, "clear": { "message": "Xoá", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "Tạo bản sao" }, - "passwordGeneratorPolicyInEffect": { - "message": "Các chính sách của tổ chức đang ảnh hưởng đến cài đặt tạo mật khẩu của bạn." - }, "passwordGenerator": { "message": "Trình tạo mật khẩu" }, @@ -2318,6 +2324,9 @@ "message": "Các tên miền", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Tên miền đã loại trừ" }, @@ -2327,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden sẽ không yêu cầu lưu thông tin đăng nhập cho các miền này. Bạn phải làm mới trang để các thay đổi có hiệu lực." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Trang Web $number$ (URI)", "placeholders": { @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Các thay đổi tên miền loại trừ đã được lưu" }, @@ -2373,14 +2394,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Tìm kiếm mục Gửi", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Thêm mục Gửi", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Văn bản" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Đã vượt số lần truy cập tối đa", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Đã hết hạn" }, - "pendingDeletion": { - "message": "Đang chờ xóa" - }, "passwordProtected": { "message": "Mật khẩu đã được bảo vệ" }, @@ -2456,24 +2462,9 @@ "message": "Sửa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "Đây là loại Send gì?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Một cái tên thân thiện để mô tả về Send này.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "Tập tin bạn muốn gửi." - }, "deletionDate": { "message": "Ngày xóa" }, - "deletionDateDesc": { - "message": "Mục Gửi sẽ được xóa vĩnh viễn vào ngày và giờ chỉ định.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "Ngày hết hạn" }, - "expirationDateDesc": { - "message": "Nếu được thiết lập, mục Gửi này sẽ hết hạn vào ngày và giờ được chỉ định.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 ngày" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "Tùy chỉnh" }, - "maximumAccessCount": { - "message": "Số lượng truy cập tối đa" - }, - "maximumAccessCountDesc": { - "message": "Nếu được thiết lập, khi đã đạt tới số lượng truy cập tối đa, người dùng sẽ không thể truy cập mục Gửi này nữa.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Yêu cầu nhập mật khẩu khi người dùng truy cập vào phần Gửi này.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Ghi chú riêng tư về Send này.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Vô hiệu hoá mục Gửi này để không ai có thể truy cập nó.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Sao chép liên kết của Send này vào khay nhớ tạm khi lưu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Văn bản bạn muốn gửi." - }, - "sendHideText": { - "message": "Ẩn văn bản của Send này theo mặc định.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Số lượng truy cập hiện tại" - }, "createSend": { "message": "Send mới", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "Trước khi bạn bắt đầu" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "Để dùng bộ chọn ngày dạng lịch", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "nhấn vào đây", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "để bật cửa sổ của bạn ra.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "Ngày hết hạn bạn nhập không hợp lệ." }, @@ -2646,15 +2588,9 @@ "dateParsingError": { "message": "Đã xảy ra lỗi khi lưu ngày xoá và ngày hết hạn của bạn." }, - "hideEmail": { - "message": "Ẩn địa chỉ email của tôi khỏi người nhận." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "Các chính sách của tổ chức đang ảnh hưởng đến tùy chọn Gửi của bạn." - }, "passwordPrompt": { "message": "Nhắc lại mật khẩu chính" }, @@ -2868,8 +2804,19 @@ "error": { "message": "Lỗi" }, - "regenerateUsername": { - "message": "Tạo lại tên người dùng" + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "Tạo tên người dùng" @@ -2877,8 +2824,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "Loại tên người dùng" + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "Địa chỉ email có hậu tố", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "Tên website" }, - "whatWouldYouLikeToGenerate": { - "message": "Bạn muốn tạo gì?" - }, - "passwordType": { - "message": "Loại mật khẩu" - }, "service": { "message": "Dịch vụ" }, @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "Gửi lại thông báo" }, - "viewAllLoginOptions": { - "message": "Xem tất cả tùy chọn đăng nhập" + "viewAllLogInOptions": { + "message": "View all log in options" + }, + "viewAllLoginOptionsV1": { + "message": "View all log in options" }, "notificationSentDevice": { "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginInitiated": { "message": "Bắt đầu đăng nhập" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "Mở trong cửa sổ mới" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Yêu cầu phê duyệt thiết bị. Chọn một tuỳ chọn phê duyệt bên dưới:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Lưu thiết bị này" }, @@ -3313,6 +3295,9 @@ "userEmailMissing": { "message": "Thiếu email người dùng" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Thiết bị tin cậy" }, @@ -3521,6 +3506,14 @@ "message": "Unlock your account, opens in a new window", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "Time-based One-Time Password Verification Code", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "Time remaining before current TOTP expires", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "Điền thông tin đăng nhập cho", "description": "Screen reader text for when overlay item is in focused" @@ -3749,6 +3742,9 @@ "accessing": { "message": "Accessing" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "passkeyNotCopied": { "message": "Không thể sao chép mã khoá" }, @@ -4240,6 +4236,21 @@ "filters": { "message": "Bộ lọc" }, + "filterVault": { + "message": "Filter vault" + }, + "filterApplied": { + "message": "One filter applied" + }, + "filterAppliedPlural": { + "message": "$COUNT$ filters applied", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "Thông tin cá nhân" }, @@ -4587,12 +4598,39 @@ "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, + "showQuickCopyActions": { + "message": "Show quick copy actions on Vault" + }, "systemDefault": { "message": "System default" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "Retry" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4787,5 +4852,56 @@ }, "generatedPassword": { "message": "Generated password" + }, + "compactMode": { + "message": "Compact mode" + }, + "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" + }, + "wide": { + "message": "Wide" + }, + "extraWide": { + "message": "Extra wide" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index e19cf428f42..dd6a2286c4a 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", + "message": "无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -50,7 +50,7 @@ "message": "提交" }, "emailAddress": { - "message": "电子邮件地址" + "message": "电子邮箱地址" }, "masterPass": { "message": "主密码" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "复制许可证号码" }, + "copyPrivateKey": { + "message": "复制私钥" + }, + "copyPublicKey": { + "message": "复制公钥" + }, + "copyFingerprint": { + "message": "复制指纹" + }, "copyCustomField": { "message": "复制 $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "自动填充身份" }, + "fillVerificationCode": { + "message": "填写验证码" + }, + "fillVerificationCodeAria": { + "message": "填写验证码", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "生成密码(并复制)" }, @@ -223,7 +239,7 @@ "message": "添加项目" }, "accountEmail": { - "message": "账户邮件地址" + "message": "账户电子邮箱" }, "requestHint": { "message": "请求提示" @@ -232,13 +248,13 @@ "message": "请求密码提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "输入您的账户电子邮件地址,您的密码提示将发送给您" + "message": "输入您的账户电子邮箱地址,您的密码提示将发送给您" }, "passwordHint": { "message": "密码提示" }, "enterEmailToGetHint": { - "message": "请输入您账户的电子邮件地址来接收主密码提示。" + "message": "请输入您的账户电子邮箱地址来接收主密码提示。" }, "getMasterPasswordHint": { "message": "获取主密码提示" @@ -268,13 +284,13 @@ "message": "前往网页 App 吗?" }, "continueToWebAppDesc": { - "message": "在网页应用上探索 Bitwarden 账户的更多功能。" + "message": "在网页 App 上探索 Bitwarden 账户的更多功能。" }, "continueToHelpCenter": { "message": "前往帮助中心吗?" }, "continueToHelpCenterDesc": { - "message": "访问帮助中心了解更多如何使用 Bitwarden 的信息。" + "message": "在帮助中心进一步了解如何使用 Bitwarden。" }, "continueToBrowserExtensionStore": { "message": "前往浏览器扩展商店吗?" @@ -283,7 +299,7 @@ "message": "帮助别人了解 Bitwarden 是否适合他们。立即访问浏览器的扩展程序商店并留下评分。" }, "changeMasterPasswordOnWebConfirmation": { - "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" + "message": "您可以在 Bitwarden 网页 App 上更改您的主密码。" }, "fingerprintPhrase": { "message": "指纹短语", @@ -336,7 +352,7 @@ "message": "免费 Bitwarden 家庭" }, "freeBitwardenFamiliesPageDesc": { - "message": "您有资格获得免费的 Bitwarden 家庭。立即在网页应用中兑换此优惠。" + "message": "您有资格获得免费的 Bitwarden 家庭。立即在网页 App 中兑换此优惠。" }, "version": { "message": "版本" @@ -363,7 +379,7 @@ "message": "文件夹名称" }, "folderHintText": { - "message": "通过在父文件夹名后面跟随一个「/」来嵌套文件夹。例如:Social/Forums" + "message": "通过在父文件夹名后面跟随「/」来嵌套文件夹。示例:Social/Forums" }, "noFoldersAdded": { "message": "未添加文件夹" @@ -412,7 +428,7 @@ "description": "Short for 'credential generator'." }, "passGenInfo": { - "message": "自动生成安全可靠唯一的登录密码。" + "message": "自动为您的登录生成强大且唯一的密码。" }, "bitWebVaultApp": { "message": "Bitwarden 网页 App" @@ -438,9 +454,6 @@ "length": { "message": "长度" }, - "passwordMinLength": { - "message": "最小密码长度" - }, "uppercase": { "message": "大写 (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -512,10 +525,6 @@ "minSpecial": { "message": "符号最少个数" }, - "avoidAmbChar": { - "message": "避免易混淆的字符", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "避免易混淆的字符", "description": "Label for the avoid ambiguous characters checkbox." @@ -588,7 +597,7 @@ "message": "前往" }, "launchWebsite": { - "message": "启动网站" + "message": "打开网站" }, "launchWebsiteName": { "message": "前往 $ITEMNAME$ 的网站", @@ -632,9 +641,6 @@ "rateExtension": { "message": "为本扩展打分" }, - "rateExtensionDesc": { - "message": "请给我们好评!" - }, "browserNotSupportClipboard": { "message": "您的浏览器不支持剪贴板简单复制,请手动复制。" }, @@ -745,10 +751,10 @@ "message": "发生了一个错误" }, "emailRequired": { - "message": "必须填写电子邮件地址。" + "message": "必须填写电子邮箱地址。" }, "invalidEmail": { - "message": "无效的电子邮件地址。" + "message": "无效的电子邮箱地址。" }, "masterPasswordRequired": { "message": "必须填写主密码。" @@ -757,7 +763,7 @@ "message": "必须填写确认主密码。" }, "masterPasswordMinlength": { - "message": "主密码必须至少 $VALUE$ 个字符长度。", + "message": "主密码长度必须至少为 $VALUE$ 个字符。", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -785,7 +791,7 @@ "message": "您可以关闭此窗口" }, "masterPassSent": { - "message": "我们已经为您发送了包含主密码提示的邮件。" + "message": "我们已经为您发送了包含主密码提示的电子邮件。" }, "verificationCodeRequired": { "message": "必须填写验证码。" @@ -807,7 +813,7 @@ } }, "autofillError": { - "message": "无法在此页面上自动填充所选项目。请改为手动复制并粘贴。" + "message": "无法在此页面上自动填充所选项目。请手动复制并粘贴。" }, "totpCaptureError": { "message": "无法从当前网页扫描二维码" @@ -828,7 +834,7 @@ "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来截取此网站的验证器二维码,或者手动复制并粘贴密钥到此字段。" }, "learnMoreAboutAuthenticators": { - "message": "了解更多关于验证器的信息" + "message": "进一步了解验证器" }, "copyTOTP": { "message": "复制验证器密钥 (TOTP)" @@ -849,13 +855,13 @@ "message": "登录到 Bitwarden" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -882,7 +888,7 @@ "message": "两步登录要求您从其他设备(例如安全钥匙、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" }, "twoStepLoginConfirmationContent": { - "message": "通过在 Bitwarden 网页 App 中设置两步登录,可以使您的账户更加安全。" + "message": "在 Bitwarden 网页 App 中设置两步登录,让您的账户更加安全。" }, "twoStepLoginConfirmationTitle": { "message": "前往网页 App 吗?" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "在标签页上列出身份项目,以便于自动填充。" }, + "clickToAutofillOnVault": { + "message": "在密码库视图中点击项目以自动填充" + }, "clearClipboard": { "message": "清空剪贴板", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1062,7 +1071,7 @@ "message": "主题" }, "themeDesc": { - "message": "更改本应用程序的颜色主题。" + "message": "更改应用程序的颜色主题。" }, "themeDescAlt": { "message": "更改应用程序的颜色主题。适用于所有已登录的账户。" @@ -1116,11 +1125,15 @@ "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" }, + "warningCapitalized": { + "message": "警告", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "确认密码库导出" }, "exportWarningDesc": { - "message": "导出的密码库数据包含未加密格式。您不应该通过不安全的渠道(例如电子邮件)来存储或发送导出的文件。用完后请立即将其删除。" + "message": "此导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。使用完后请立即将其删除。" }, "encExportKeyWarningDesc": { "message": "此导出将使用您账户的加密密钥来加密您的数据。如果您曾经轮换过账户的加密密钥,您应将其重新导出,否则您将无法解密导出的文件。" @@ -1140,9 +1153,6 @@ "moveToOrganization": { "message": "移动到组织" }, - "share": { - "message": "共享" - }, "movedItemToOrg": { "message": "$ITEMNAME$ 已移动到 $ORGNAME$", "placeholders": { @@ -1306,8 +1316,14 @@ "enterVerificationCodeApp": { "message": "请输入您的验证器 App 中的 6 位数验证码。" }, + "authenticationTimeout": { + "message": "身份验证超时" + }, + "authenticationSessionTimedOut": { + "message": "身份验证会话超时。请重新启动登录过程。" + }, "enterVerificationCodeEmail": { - "message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。", + "message": "请输入发送给 $EMAIL$ 的 6 位数验证码。", "placeholders": { "email": { "content": "$1", @@ -1384,7 +1400,7 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "为您的组织使用 Duo Security 的 Duo 移动应用、短信、电话或 U2F 安全钥匙来进行验证。", + "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全钥匙来进行验证。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { @@ -1394,7 +1410,7 @@ "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" }, "emailTitle": { - "message": "电子邮件" + "message": "电子邮箱" }, "emailDescV2": { "message": "输入发送到您的电子邮箱的代码。" @@ -1412,7 +1428,7 @@ "message": "对于高级配置,您可以单独指定每个服务的基础 URL。" }, "selfHostedEnvFormInvalid": { - "message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。" + "message": "您必须添加基础服务器 URL 或至少一个自定义环境。" }, "customEnvironment": { "message": "自定义环境" @@ -1494,27 +1510,14 @@ "enableAutoFillOnPageLoadDesc": { "message": "网页加载时如果检测到登录表单,则执行自动填充。" }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$警告:$CLOSETAG$不完整或不信任的网站可以利用页面加载时的自动填充功能。", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "不完整或不信任的网站可以利用页面加载时的自动填充功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "了解更多关于风险的信息" + "message": "进一步了解风险" }, "learnMoreAboutAutofill": { - "message": "了解更多关于自动填充的信息" + "message": "进一步了解自动填充" }, "defaultAutoFillOnPageLoad": { "message": "登录项目的默认自动填充设置" @@ -1717,7 +1720,7 @@ "message": "许可证号码" }, "email": { - "message": "电子邮件" + "message": "电子邮箱" }, "phone": { "message": "电话" @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "身份" }, + "typeSshKey": { + "message": "SSH 密钥" + }, "newItemHeader": { "message": "新增 $TYPE$", "placeholders": { @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "安全笔记" }, + "sshKeys": { + "message": "SSH 密钥" + }, "clear": { "message": "清除", "description": "To clear something out. example: To clear browser history." @@ -2031,9 +2040,6 @@ "clone": { "message": "克隆" }, - "passwordGeneratorPolicyInEffect": { - "message": "一个或多个组织策略正在影响您的生成器设置。" - }, "passwordGenerator": { "message": "密码生成器" }, @@ -2117,7 +2123,7 @@ "message": "您仍然想要填充此登录信息吗?" }, "autofillIframeWarning": { - "message": "该表单由不同于您保存的登录的 URI 域名托管。选择「确定」以自动填充,或选择「取消」停止填充。" + "message": "该表单由与您保存的登录 URI 不同的域名托管。选择「确定」继续自动填充,或选择「取消」停止自动填充。" }, "autofillIframeWarningTip": { "message": "要防止以后出现此警告,请将此站点的 URI $HOSTNAME$ 保存到您的 Bitwarden 登录项目中。", @@ -2189,13 +2195,13 @@ "message": "取消订阅" }, "atAnyTime": { - "message": "随时" + "message": "随时。" }, "byContinuingYouAgreeToThe": { "message": "若继续,代表您同意" }, "and": { - "message": "以及" + "message": "和" }, "acceptPolicies": { "message": "选中此框表示您同意:" @@ -2312,12 +2318,15 @@ "message": "一个组织策略正影响您的所有权选项。" }, "personalOwnershipPolicyInEffectImports": { - "message": "组织策略已阻止将项目导入您的个人密码库。" + "message": "某个组织策略已阻止将项目导入您的个人密码库。" }, "domainsTitle": { "message": "域名", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "屏蔽域名" + }, "excludedDomains": { "message": "排除域名" }, @@ -2325,7 +2334,16 @@ "message": "Bitwarden 将不会询问是否为这些域名保存登录信息。您必须刷新页面才能使更改生效。" }, "excludedDomainsDescAlt": { - "message": "Bitwarden 不会询问保存所有已登录的账户的这些域名的登录信息。必须刷新页面才能使更改生效。" + "message": "Bitwarden 将不会询问是否为所有已登录账户的这些域名保存登录信息。您必须刷新页面才能使更改生效。" + }, + "blockedDomainsDesc": { + "message": "将不会为这些网站提供自动填充和其他相关功能。您必须刷新页面才能使更改生效。" + }, + "autofillBlockedNotice": { + "message": "该网站的自动填充功能已被阻止。请在设置中查看或更改。" + }, + "autofillBlockedTooltip": { + "message": "该网站的自动填充功能已被阻止。请在设置中查看。" }, "websiteItemLabel": { "message": "网站 $number$ (URI)", @@ -2345,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "屏蔽域名更改已保存" + }, "excludedDomainsSavedSuccess": { "message": "排除域名更改已保存" }, @@ -2352,7 +2373,7 @@ "message": "限制查看" }, "limitSendViewsHint": { - "message": "在达到限额后,任何人无法查看此 Send。", + "message": "达到限额后,任何人无法查看此 Send。", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { @@ -2373,14 +2394,6 @@ "message": "Send 详细信息", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "搜索 Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "添加 Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "文本" }, @@ -2397,16 +2410,9 @@ "hideTextByDefault": { "message": "默认隐藏文本" }, - "maxAccessCountReached": { - "message": "已达最大访问次数", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "已过期" }, - "pendingDeletion": { - "message": "等待删除" - }, "passwordProtected": { "message": "密码保护" }, @@ -2456,24 +2462,9 @@ "message": "编辑 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "这是什么类型的 Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "用于描述此 Send 的友好名称。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "您想要发送的文件。" - }, "deletionDate": { "message": "删除日期" }, - "deletionDateDesc": { - "message": "此 Send 将在指定的日期和时间后被永久删除。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "此 Send 将在此日期后被永久删除。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2481,10 +2472,6 @@ "expirationDate": { "message": "过期日期" }, - "expirationDateDesc": { - "message": "设置后,对此 Send 的访问将在指定的日期和时间后过期。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 天" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "自定义" }, - "maximumAccessCount": { - "message": "最大访问次数" - }, - "maximumAccessCountDesc": { - "message": "设置后,当达到最大访问次数时用户将不再能访问此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "可选,用户需要提供密码才能访问此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "添加一个用于收件人访问此 Send 的可选密码。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "关于此 Send 的私密备注。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "停用此 Send 则任何人无法访问它。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "保存时复制此 Send 的链接到剪贴板。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "您想要发送的文本。" - }, - "sendHideText": { - "message": "默认隐藏此 Send 的文本。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "当前访问次数" - }, "createSend": { "message": "创建 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2619,18 +2573,6 @@ "sendFileCalloutHeader": { "message": "在开始之前" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "要使用日历样式的日期选择器", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "点击这里", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "来弹出窗口。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "所提供的过期日期无效。" }, @@ -2646,14 +2588,8 @@ "dateParsingError": { "message": "保存您的删除和过期日期时出错。" }, - "hideEmail": { - "message": "对收件人隐藏我的电子邮件地址。" - }, "hideYourEmail": { - "message": "对查看者隐藏您的电子邮件地址。" - }, - "sendOptionsPolicyInEffect": { - "message": "一个或多个组织策略正在影响您的 Send 选项。" + "message": "对查看者隐藏您的电子邮箱地址。" }, "passwordPrompt": { "message": "主密码重新提示" @@ -2665,13 +2601,13 @@ "message": "此操作受到保护。若要继续,请重新输入您的主密码以验证您的身份。" }, "emailVerificationRequired": { - "message": "需要验证电子邮件" + "message": "需要验证电子邮箱" }, "emailVerifiedV2": { "message": "电子邮箱已验证" }, "emailVerificationRequiredDesc": { - "message": "您必须验证电子邮件才能使用此功能。您可以在网页密码库中验证您的电子邮件。" + "message": "您必须验证电子邮箱才能使用此功能。您可以在网页密码库中验证您的电子邮箱。" }, "updatedMasterPassword": { "message": "已更新主密码" @@ -2680,7 +2616,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2868,17 +2804,28 @@ "error": { "message": "错误" }, - "regenerateUsername": { - "message": "重新生成用户名" + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "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": "生成邮件地址" + "message": "生成电子邮箱" }, - "generatorBoundariesHint": { - "message": "值必须在 $MIN$ 和 $MAX$ 之间", + "spinboxBoundariesHint": { + "message": "值必须在 $MIN$ 和 $MAX$ 之间。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,18 +2838,35 @@ } } }, - "usernameType": { - "message": "用户名类型" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { - "message": "附加地址电子邮件", + "message": "附加地址电子邮箱", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "使用您的电子邮件供应商的子地址功能。" + "message": "使用您的电子邮箱提供商的子地址功能。" }, "catchallEmail": { - "message": "Catch-all 电子邮件" + "message": "Catch-all 电子邮箱" }, "catchallEmailDesc": { "message": "使用您的域名配置的 Catch-all 收件箱。" @@ -2916,23 +2880,17 @@ "websiteName": { "message": "网站名称" }, - "whatWouldYouLikeToGenerate": { - "message": "您想要生成什么?" - }, - "passwordType": { - "message": "密码类型" - }, "service": { "message": "服务" }, "forwardedEmail": { - "message": "转发的电子邮件别名" + "message": "转发的电子邮箱别名" }, "forwardedEmailDesc": { - "message": "使用外部转发服务生成一个电子邮件别名。" + "message": "使用外部转发服务生成一个电子邮箱别名。" }, "forwarderDomainName": { - "message": "邮件域名", + "message": "电子邮箱域名", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2992,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "无法获取 $SERVICENAME$ 电子邮件账户 ID。", + "message": "无法获取 $SERVICENAME$ 电子邮箱账户 ID。", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3121,7 +3079,7 @@ "message": "初来乍到吗?" }, "rememberEmail": { - "message": "记住电子邮件地址" + "message": "记住电子邮箱" }, "loginWithDevice": { "message": "使用设备登录" @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "重新发送通知" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "查看所有登录选项" + }, + "viewAllLoginOptionsV1": { "message": "查看所有登录选项" }, "notificationSentDevice": { "message": "通知已发送到您的设备。" }, + "aNotificationWasSentToYourDevice": { + "message": "通知已发送到您的设备" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "确保您的账户已解锁,并且指纹短语与其他设备上的相匹配。" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "请求获得批准后,您将收到通知" + }, + "needAnotherOptionV1": { + "message": "需要其他选项吗?" + }, "loginInitiated": { "message": "登录已发起" }, @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "在新窗口中打开" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "记住此设备以便将来无缝登录" + }, "deviceApprovalRequired": { "message": "需要设备批准。请在下面选择一个批准选项:" }, + "deviceApprovalRequiredV2": { + "message": "需要设备批准" + }, + "selectAnApprovalOptionBelow": { + "message": "在下方选择一个批准选项" + }, "rememberThisDevice": { "message": "记住此设备" }, @@ -3259,7 +3241,7 @@ "message": "必须填写组织 SSO 标识符。" }, "creatingAccountOn": { - "message": "正创建账户于" + "message": "创建账户至" }, "checkYourEmail": { "message": "检查您的电子邮箱" @@ -3277,7 +3259,7 @@ "message": "返回" }, "toEditYourEmailAddress": { - "message": "编辑您的电子邮件地址。" + "message": "编辑您的电子邮箱地址。" }, "eu": { "message": "欧盟", @@ -3311,7 +3293,10 @@ "message": "登录已批准" }, "userEmailMissing": { - "message": "缺少用户电子邮件" + "message": "缺少用户电子邮箱" + }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "未找到活动的用户电子邮箱。您将被注销。" }, "deviceTrusted": { "message": "设备已信任" @@ -3379,14 +3364,14 @@ } }, "multipleInputEmails": { - "message": "一个或多个电子邮件地址无效" + "message": "一个或多个电子邮箱无效" }, "inputTrimValidator": { "message": "输入不能只包含空格。", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "输入的不是电子邮件地址。" + "message": "输入的不是电子邮箱地址。" }, "fieldsNeedAttention": { "message": "上面的 $COUNT$ 个字段需要您注意。", @@ -3425,7 +3410,7 @@ "message": "清除全部" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "还有 $QUANTITY$ 个", "placeholders": { "quantity": { "content": "$1", @@ -3457,11 +3442,11 @@ "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "导入中...", + "message": "正在导入...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "数据导入成功!", + "message": "数据成功导入!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { @@ -3491,7 +3476,7 @@ "message": "切换侧边导航" }, "skipToContent": { - "message": "跳转到正文" + "message": "跳转到内容" }, "bitwardenOverlayButton": { "message": "Bitwarden 自动填充菜单按钮", @@ -3521,6 +3506,14 @@ "message": "解锁您的账户(在新窗口中打开)", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, + "totpCodeAria": { + "message": "基于时间的一次性密码验证码", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "TOTP 到期前剩余时间", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "为其填写凭据", "description": "Screen reader text for when overlay item is in focused" @@ -3652,7 +3645,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 寻求帮助。" + "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 获取协助。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 DUO 并按照步骤完成登录。" @@ -3749,6 +3742,9 @@ "accessing": { "message": "正在访问" }, + "loggedInExclamation": { + "message": "已登录!" + }, "passkeyNotCopied": { "message": "通行密钥不会被复制" }, @@ -3959,7 +3955,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "将 Bitwarden 设置为您的默认密码管理器吗?", + "message": "将 Bitwarden 设置为默认密码管理器吗?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -3967,7 +3963,7 @@ "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "将 Bitwarden 设置为您的默认密码管理器", + "message": "将 Bitwarden 设置为默认密码管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { @@ -4015,7 +4011,7 @@ "message": "自动填充建议" }, "autofillSuggestionsTip": { - "message": "保存此站点的登录项目用来自动填充" + "message": "将此站点保存为登录项目以用于自动填充" }, "yourVaultIsEmpty": { "message": "您的密码库是空的" @@ -4024,7 +4020,7 @@ "message": "没有搜索到匹配的项目" }, "clearFiltersOrTryAnother": { - "message": "清除筛选器或尝试另一个搜索词" + "message": "清除筛选或尝试另一个搜索词" }, "copyInfoTitle": { "message": "复制信息 - $ITEMNAME$", @@ -4093,7 +4089,7 @@ "message": "分配到集合" }, "copyEmail": { - "message": "复制电子邮件地址" + "message": "复制电子邮箱" }, "copyPhone": { "message": "复制电话号码" @@ -4196,7 +4192,7 @@ "message": "所有者:您" }, "linked": { - "message": "已链接" + "message": "链接型" }, "copySuccessful": { "message": "复制成功" @@ -4240,6 +4236,21 @@ "filters": { "message": "筛选" }, + "filterVault": { + "message": "密码库筛选" + }, + "filterApplied": { + "message": "已应用一个筛选" + }, + "filterAppliedPlural": { + "message": "已应用 $COUNT$ 个筛选", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "personalDetails": { "message": "个人详细信息" }, @@ -4409,10 +4420,10 @@ "message": "对于如密码之类的敏感数据,请使用隐藏型字段" }, "checkBoxHelpText": { - "message": "如果您想自动勾选表单复选框(例如记住电子邮件地址),请使用复选框" + "message": "如果您想自动勾选表单复选框(例如记住电子邮箱),请使用复选框型字段" }, "linkedHelpText": { - "message": "当您处理特定网站的自动填充问题时,请使用链接型字段。" + "message": "当您处理特定网站的自动填充问题时,请使用链接型字段" }, "linkedLabelHelpText": { "message": "输入字段的 html id、名称、aria-label 或占位符。" @@ -4585,7 +4596,10 @@ "message": "账户操作" }, "showNumberOfAutofillSuggestions": { - "message": "在扩展图标上显示自动填充建议的登录的数量" + "message": "在扩展图标上显示自动填充建议的登录数量" + }, + "showQuickCopyActions": { + "message": "在密码库上显示快速复制操作" }, "systemDefault": { "message": "跟随系统" @@ -4593,6 +4607,30 @@ "enterprisePolicyRequirementsApplied": { "message": "企业策略要求已应用于此设置" }, + "sshPrivateKey": { + "message": "私钥" + }, + "sshPublicKey": { + "message": "公钥" + }, + "sshFingerprint": { + "message": "指纹" + }, + "sshKeyAlgorithm": { + "message": "密钥类型" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "retry": { "message": "重试" }, @@ -4632,6 +4670,33 @@ "noEditPermissions": { "message": "您没有编辑此项目的权限" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "生物识别解锁不可用,因为 Bitwarden 桌面 App 已关闭。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authenticating": { "message": "正在验证" }, @@ -4680,7 +4745,7 @@ "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "脱字符", + "message": "插入符", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4786,6 +4851,57 @@ "message": "大写" }, "generatedPassword": { - "message": "生成密码" + "message": "生成了密码" + }, + "compactMode": { + "message": "紧凑模式" + }, + "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": "扩展宽度" + }, + "wide": { + "message": "宽" + }, + "extraWide": { + "message": "超宽" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 26bbbd0054f..467deffd815 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -20,16 +20,16 @@ "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": "設定一個強密碼" @@ -84,7 +84,7 @@ "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +120,7 @@ "message": "複製密碼" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "複製密碼短語" }, "copyNote": { "message": "複製備註" @@ -152,6 +152,15 @@ "copyLicenseNumber": { "message": "複製駕照號碼" }, + "copyPrivateKey": { + "message": "複製私密金鑰" + }, + "copyPublicKey": { + "message": "複製公開金鑰" + }, + "copyFingerprint": { + "message": "複製指紋" + }, "copyCustomField": { "message": "複製 $FIELD$", "placeholders": { @@ -183,6 +192,13 @@ "autoFillIdentity": { "message": "自動填入身分資訊" }, + "fillVerificationCode": { + "message": "填入驗證碼" + }, + "fillVerificationCodeAria": { + "message": "填入驗證碼", + "description": "Aria label for the heading displayed the inline menu for totp code autofill" + }, "generatePasswordCopied": { "message": "產生及複製密碼" }, @@ -232,7 +248,7 @@ "message": "請求密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "輸入您的帳號電子郵件,您的密碼提示會傳送給您" + "message": "輸入您帳號的電子郵件,您的密碼提示會傳送給您" }, "passwordHint": { "message": "密碼提示" @@ -265,7 +281,7 @@ "message": "變更主密碼" }, "continueToWebApp": { - "message": "接下來造訪 Web App 嗎?" + "message": "接下來造訪網頁 App 嗎?" }, "continueToWebAppDesc": { "message": "在 Web 應用程式上探索 Bitwarden 帳戶的更多功能。" @@ -318,25 +334,25 @@ "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 網站上了解更多。" }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "免費的 Bitwarden 家庭方案" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "您符合免費 Bitwarden 家庭方案的資格要求。在網頁應用程式中兌換您的優惠。" }, "version": { "message": "版本" @@ -363,16 +379,16 @@ "message": "資料夾名稱" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "在資料夾名稱後面使用「/」來建立樹狀結構。\n例如:社交網路/論壇" }, "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": "刪除資料夾" @@ -415,7 +431,7 @@ "message": "自動產生安全、唯一的登入密碼。" }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden 網頁應用程式" }, "importItems": { "message": "匯入項目" @@ -427,7 +443,7 @@ "message": "產生密碼" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "產生密碼短語" }, "regeneratePassword": { "message": "重新產生密碼" @@ -438,9 +454,6 @@ "length": { "message": "長度" }, - "passwordMinLength": { - "message": "最小密碼長度" - }, "uppercase": { "message": "大寫 (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -462,7 +475,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "包含大寫字元", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -470,7 +483,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": { @@ -478,7 +491,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": { @@ -486,7 +499,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" }, "specialCharactersLabel": { @@ -512,16 +525,12 @@ "minSpecial": { "message": "最少符號位數" }, - "avoidAmbChar": { - "message": "避免易混淆的字元", - "description": "deprecated. Use avoidAmbiguous instead." - }, "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": { @@ -555,19 +564,19 @@ "message": "我的最愛" }, "unfavorite": { - "message": "Unfavorite" + "message": "取消最愛" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "項目已加入到最愛" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "項目已從最愛中移除" }, "notes": { "message": "備註" }, "privateNote": { - "message": "Private note" + "message": "私人備註" }, "note": { "message": "備註" @@ -591,7 +600,7 @@ "message": "開啟網站" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "開啟網站 $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,9 +641,6 @@ "rateExtension": { "message": "為本套件評分" }, - "rateExtensionDesc": { - "message": "請給予我們好評!" - }, "browserNotSupportClipboard": { "message": "您的瀏覽器不支援剪貼簿簡單複製,請手動複製。" }, @@ -645,7 +651,7 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "您的密碼庫已被鎖定" }, "yourAccountIsLocked": { "message": "您的帳戶已被鎖定。" @@ -736,7 +742,7 @@ "message": "主密碼" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "若您忘記主密碼,將會無法找回!" }, "masterPassHintLabel": { "message": "您已成功創建新帳戶!" @@ -776,7 +782,7 @@ "message": "您已成功創建新帳戶!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "你已經登入!" }, "youSuccessfullyLoggedIn": { "message": "登入成功" @@ -791,7 +797,7 @@ "message": "必須填入驗證碼。" }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "驗證已被取消或時間超過。請再試一次。" }, "invalidVerificationCode": { "message": "無效的驗證碼" @@ -819,16 +825,16 @@ "message": "從目前網頁掃描驗證器二維碼" }, "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 code,或手動複製金鑰並貼上到此欄位。" }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "了解更多驗證程式" }, "copyTOTP": { "message": "複製驗證器金鑰 (TOTP)" @@ -846,19 +852,19 @@ "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" }, "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": "您確定要登出嗎?" @@ -882,10 +888,10 @@ "message": "兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 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": "前往網頁 App 嗎?" }, "editedFolder": { "message": "資料夾已儲存" @@ -928,7 +934,7 @@ "message": "新增 URI" }, "addDomain": { - "message": "Add domain", + "message": "新增網域", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -981,7 +987,7 @@ "message": "如果在您的密碼庫中找不到項目,則詢問是否新增項目。適用於所有已登入的帳戶。" }, "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "message": "在密碼庫介面中顯示支付卡自動填入建議" }, "showCardsCurrentTab": { "message": "於分頁頁面顯示支付卡" @@ -990,7 +996,7 @@ "message": "於分頁頁面顯示信用卡以便於自動填入。" }, "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "message": "在密碼庫介面中顯示身分自動填入建議" }, "showIdentitiesCurrentTab": { "message": "於分頁頁面顯示身分" @@ -998,6 +1004,9 @@ "showIdentitiesCurrentTabDesc": { "message": "於分頁頁面顯示身分以便於自動填入。" }, + "clickToAutofillOnVault": { + "message": "在密碼庫檢視中點擊項目來自動填入" + }, "clearClipboard": { "message": "清除剪貼簿", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1080,7 +1089,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "匯出自" }, "exportVault": { "message": "匯出密碼庫" @@ -1089,7 +1098,7 @@ "message": "檔案格式" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "此檔案匯出將受密碼保護,需要檔案密碼才能解密。" }, "filePassword": { "message": "檔案密碼" @@ -1098,24 +1107,28 @@ "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": "匯出類型" }, "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": "警告", + "description": "Warning (should maintain locale-relevant capitalization)" + }, "confirmVaultExport": { "message": "確認匯出密碼庫" }, @@ -1135,14 +1148,11 @@ "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 可讓您透過組織與其他人共用您的密碼庫項目。請造訪 bitwarden.com 網站以了解更多資訊。" }, "moveToOrganization": { "message": "移動至組織 " }, - "share": { - "message": "共用" - }, "movedItemToOrg": { "message": "已將 $ITEMNAME$ 移動至 $ORGNAME$", "placeholders": { @@ -1196,7 +1206,7 @@ "message": "檔案" }, "fileToShare": { - "message": "File to share" + "message": "要分享的文件" }, "selectFile": { "message": "選取檔案" @@ -1232,7 +1242,7 @@ "message": "用於檔案附件的 1 GB 加密儲存空間。" }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "緊急存取" }, "premiumSignUpTwoStepOptions": { "message": "專有的兩步驟登入選項,例如 YubiKey 和 Duo。" @@ -1256,7 +1266,7 @@ "message": "您可以在 bitwarden.com 網頁版密碼庫購買進階會員資格。現在要前往嗎?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁 App 的帳號設定中購買進階版。" }, "premiumCurrentMember": { "message": "您目前為進階會員!" @@ -1265,7 +1275,7 @@ "message": "感謝您支持 Bitwarden 。" }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "升級到進階版並接收:" }, "premiumPrice": { "message": "每年只需 $PRICE$!", @@ -1277,7 +1287,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "每年只需 $PRICE$!", "placeholders": { "price": { "content": "$1", @@ -1306,6 +1316,12 @@ "enterVerificationCodeApp": { "message": "輸入驗證器應用程式提供的 6 位數驗證碼。" }, + "authenticationTimeout": { + "message": "驗證逾時" + }, + "authenticationSessionTimedOut": { + "message": "驗證工作階段因時間過久已逾時。請重試登入。" + }, "enterVerificationCodeEmail": { "message": "輸入已傳送至 $EMAIL$ 的 6 位數驗證碼。", "placeholders": { @@ -1370,17 +1386,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1397,7 +1413,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "selfHostedEnvironment": { "message": "自我裝載環境" @@ -1406,13 +1422,13 @@ "message": "指定您內部部署的 Bitwarden 安裝之基礎 URL。" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 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": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" @@ -1424,7 +1440,7 @@ "message": "伺服器 URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1450,22 +1466,22 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "在表單欄位上顯示自動填入選單" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "顯示身分建議" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "顯示信用卡建議" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "當選擇圖示時,顯示建議" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "適用於所有已登入的帳戶。" }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "關閉你的瀏覽器內建密碼管理器設定以避免衝突。" @@ -1486,7 +1502,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": "頁面載入時自動填入" @@ -1494,24 +1510,11 @@ "enableAutoFillOnPageLoadDesc": { "message": "網頁載入時如果偵測到登入表單,則執行自動填入。" }, - "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", - "placeholders": { - "openTag": { - "content": "$1", - "example": "" - }, - "closeTag": { - "content": "$2", - "example": "" - } - } - }, "experimentalFeature": { "message": "被入侵或不被信任的網站,可能會濫用頁面載入的自動填入功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "了解更多風險" }, "learnMoreAboutAutofill": { "message": "進一步瞭解「自動填入」功能" @@ -1541,13 +1544,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": "產生一組新的隨機密碼並將它複製到剪貼簿中。" @@ -1764,6 +1767,9 @@ "typeIdentity": { "message": "身分" }, + "typeSshKey": { + "message": "SSH 金鑰" + }, "newItemHeader": { "message": "新增 $TYPE$", "placeholders": { @@ -1795,13 +1801,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": "返回" @@ -1839,6 +1845,9 @@ "secureNotes": { "message": "安全筆記" }, + "sshKeys": { + "message": "SSH 金鑰" + }, "clear": { "message": "清除", "description": "To clear something out. example: To clear browser history." @@ -1920,10 +1929,10 @@ "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "remove": { "message": "移除" @@ -1993,7 +2002,7 @@ "message": "設定您用來解鎖 Bitwarden 的 PIN 碼。您的 PIN 設定將在您完全登出本應用程式時被重設。" }, "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": "您的 PIN 碼會取代主密碼用來解鎖 Bitwarden。您的 PIN 碼會重置,若您完全登出 Bitwarden。" }, "pinRequired": { "message": "必須填入 PIN 碼。" @@ -2008,7 +2017,7 @@ "message": "使用生物特徵辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "awaitDesktop": { "message": "等待來自桌面應用程式的確認" @@ -2020,7 +2029,7 @@ "message": "瀏覽器重啟後使用主密碼鎖定" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "瀏覽器重啟後使用主密碼鎖定" }, "selectOneCollection": { "message": "您必須至少選擇一個集合。" @@ -2031,30 +2040,27 @@ "clone": { "message": "克隆" }, - "passwordGeneratorPolicyInEffect": { - "message": "一個或多個組織原則正影響密碼產生器設定。" - }, "passwordGenerator": { - "message": "Password generator" + "message": "密碼產生器" }, "usernameGenerator": { - "message": "Username generator" + "message": "使用者名稱產生器" }, "useThisPassword": { - "message": "Use this password" + "message": "使用此密碼" }, "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'" }, "vaultTimeoutAction": { @@ -2090,7 +2096,7 @@ "message": "項目已還原" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "已經有帳號了嗎?" }, "vaultTimeoutLogOutConfirmation": { "message": "選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,若要重新驗證則需連線網路。確定要使用此設定嗎?" @@ -2102,7 +2108,7 @@ "message": "自動填入並儲存" }, "fillAndSave": { - "message": "Fill and save" + "message": "填入並儲存" }, "autoFillSuccessAndSavedUri": { "message": "項目已自動填入並且已儲存統一資源標識符(URI)" @@ -2183,19 +2189,19 @@ "message": "您新的主密碼不符合原則要求。" }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "獲得來自 Bitwarden 的公告、建議及研究資訊電子郵件。" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "取消訂閱" }, "atAnyTime": { - "message": "at any time." + "message": "在任何時間。" }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "若是繼續,則代表您同意" }, "and": { - "message": "and" + "message": "和" }, "acceptPolicies": { "message": "選中此選取框,即表示您同意下列條款:" @@ -2216,10 +2222,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": "桌面同步驗證" @@ -2258,10 +2264,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": "生物特徵辨識未設定" @@ -2276,16 +2282,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": "生物特徵辨識失敗" @@ -2315,9 +2321,12 @@ "message": "某個組織原則已禁止您將項目匯入至您的個人密碼庫。" }, "domainsTitle": { - "message": "Domains", + "message": "網域", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "已封鎖的網域" + }, "excludedDomains": { "message": "排除網域" }, @@ -2327,8 +2336,17 @@ "excludedDomainsDescAlt": { "message": "對於所有已登入的帳戶,Bitwarden 不會詢問是否儲存這些網域的登入資訊。您必須重新整理頁面變更才會生效。" }, + "blockedDomainsDesc": { + "message": "自動填入及其它相關的功能無法在這些網站上使用。您必須重新整理頁面來使變更生效。" + }, + "autofillBlockedNotice": { + "message": "自動填入已在此網站被封鎖。請在設定中檢視或更改此限制。" + }, + "autofillBlockedTooltip": { + "message": "自動填入已在此網站被封鎖。請在設定中檢視。" + }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "網站 $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2345,18 +2363,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "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": "在達到限額後,沒有人能查看此 Send。", "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": { @@ -2370,22 +2391,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "searchSends": { - "message": "搜尋 Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "新增 Send", + "message": "Send 詳細資訊", "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": "檔案" @@ -2395,23 +2408,16 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" - }, - "maxAccessCountReached": { - "message": "已達最大存取次數", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + "message": "默認隱藏文字" }, "expired": { "message": "已逾期" }, - "pendingDeletion": { - "message": "等待刪除" - }, "passwordProtected": { "message": "密碼保護" }, "copyLink": { - "message": "Copy link" + "message": "複製連結" }, "copySendLink": { "message": "複製 Send 連結", @@ -2449,42 +2455,23 @@ "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": "您確定要永久刪除此 Send 嗎?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { "message": "編輯 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "這是什麽類型的 Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "用於描述此 Send 的易記名稱。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "您想要傳送的檔案。" - }, "deletionDate": { "message": "刪除日期" }, - "deletionDateDesc": { - "message": "此 Send 將在指定的日期和時間後被永久刪除。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "此 Send 將在指定的日期後被永久刪除。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "逾期日期" }, - "expirationDateDesc": { - "message": "如果設定此選項,對此 Send 的存取將在指定的日期和時間後逾期。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 天" }, @@ -2500,43 +2487,10 @@ "custom": { "message": "自訂" }, - "maximumAccessCount": { - "message": "最大存取次數" - }, - "maximumAccessCountDesc": { - "message": "如果設定此選項,當達到最大存取次數時,使用者將無法再次存取此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "選用功能。使用者需提供密碼才能存取此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "關於此 Send 的私人備註。", + "message": "新增一個用於收件人存取此 Send 的可選密碼。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendDisableDesc": { - "message": "停用此 Send 以阻止任何人存取。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "儲存時複製此 Send 的連結至剪貼簿。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "您想要傳送的文字。" - }, - "sendHideText": { - "message": "預設隱藏此 Send 的文字。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "目前存取次數" - }, "createSend": { "message": "建立新 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2557,15 +2511,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": "Send 創建成功!", "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": "在接下來的 1 小時內,任何人都可以透過連結訪問此 Send。", "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$ 小時內,任何人都可以透過連結訪問此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2575,11 +2529,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "在接下來的 1 天內,任何人都可以透過連結訪問此 Send。", "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$ 天內,任何人都可以透過連結訪問此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2589,7 +2543,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "已複製 Send 連結", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2597,11 +2551,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": "要建立檔案 Send,您需要彈出擴充程式到新視窗。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2614,23 +2568,11 @@ "message": "要使用 Safari 來選擇檔案,請點選此橫幅彈出至新視窗。" }, "popOut": { - "message": "Pop out" + "message": "彈出" }, "sendFileCalloutHeader": { "message": "在開始之前" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "使用行事曆樣式的日期選擇器", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "點選此處", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "要彈出至視窗。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "指定的逾期日期無效。" }, @@ -2646,14 +2588,8 @@ "dateParsingError": { "message": "儲存刪除日期和逾期日期時發生錯誤。" }, - "hideEmail": { - "message": "對收件人隱藏我的電子郵件地址。" - }, "hideYourEmail": { - "message": "Hide your email address from viewers." - }, - "sendOptionsPolicyInEffect": { - "message": "一個或多個組織原則正影響您的 Send 選項。" + "message": "對查看者隱藏您的電子郵件地址。" }, "passwordPrompt": { "message": "重新詢問主密碼" @@ -2668,7 +2604,7 @@ "message": "需要驗證電子郵件" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "電子郵件已驗證" }, "emailVerificationRequiredDesc": { "message": "您必須驗證您的電子郵件才能使用此功能。您可以在網頁密碼庫裡驗證您的電子郵件。" @@ -2686,7 +2622,7 @@ "message": "您的主密碼不符合一個或多個組織政策規定。您必須立即更新您的主密碼才能存取密碼庫。進行此動作將登出您目前的工作階段,需要您重新登入。其他裝置上的工作階段可能持續長達一小時。" }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "您的組織停用了信任裝置加密。若要存取您的密碼庫,請設定主密碼。" }, "resetPasswordPolicyAutoEnroll": { "message": "自動註冊" @@ -2710,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "$TOTAL$ 不足", "placeholders": { "total": { "content": "$1", @@ -2729,7 +2665,7 @@ "message": "分鐘" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "企業政策已套用至您的逾時選項中" }, "vaultTimeoutPolicyInEffect": { "message": "您的組織政策已限定您密碼庫逾時的時間長度。密碼庫逾時時間最高可以設定到 $HOURS$ 小時 $MINUTES$ 分鐘。", @@ -2745,7 +2681,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "最多 $HOURS$ 小時 $MINUTES$ 分鐘", "placeholders": { "hours": { "content": "$1", @@ -2758,7 +2694,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", @@ -2854,10 +2790,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", @@ -2868,17 +2804,28 @@ "error": { "message": "錯誤" }, - "regenerateUsername": { - "message": "重新產生使用者名稱" + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "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": "生成電子郵件" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "數值必須在 $MIN$ 和 $MAX$ 之間。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2891,8 +2838,25 @@ } } }, - "usernameType": { - "message": "使用者名稱類型" + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } }, "plusAddressedEmail": { "message": "加號地址電子郵件", @@ -2916,12 +2880,6 @@ "websiteName": { "message": "網站名稱" }, - "whatWouldYouLikeToGenerate": { - "message": "您想要產生什麼?" - }, - "passwordType": { - "message": "密碼類型" - }, "service": { "message": "服務" }, @@ -2932,15 +2890,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": { @@ -2954,11 +2912,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": { @@ -2968,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "無效的 $SERVICENAME$ API 權杖", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2978,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "無效的 $SERVICENAME$ API 權杖:$ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2992,7 +2950,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": { @@ -3002,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "無效的 $SERVICENAME$ 網域。", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3012,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "無效的 $SERVICENAME$ URI。", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3022,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "發生未知的 $SERVICENAME$ 錯誤。", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3032,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "未知的轉送服務提供商:$SERVICENAME$。", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3138,12 +3096,27 @@ "resendNotification": { "message": "重新傳送通知" }, - "viewAllLoginOptions": { + "viewAllLogInOptions": { + "message": "檢視所有登入選項" + }, + "viewAllLoginOptionsV1": { "message": "檢視所有登入選項" }, "notificationSentDevice": { "message": "已傳送通知至您的裝置。" }, + "aNotificationWasSentToYourDevice": { + "message": "已傳送通知至您的裝置" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "請確保您的帳號已解鎖,並且指紋短語與其他裝置一致。" + }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "一旦您的請求被通過,您會獲得通知。" + }, + "needAnotherOptionV1": { + "message": "需要另一個選項嗎?" + }, "loginInitiated": { "message": "登入已啟動" }, @@ -3202,22 +3175,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", @@ -3237,9 +3210,18 @@ "opensInANewWindow": { "message": "在新視窗開啟" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "記住此裝置來讓未來的登入體驗更簡易" + }, "deviceApprovalRequired": { "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, + "deviceApprovalRequiredV2": { + "message": "需要核准裝置" + }, + "selectAnApprovalOptionBelow": { + "message": "選擇下面的一個核准選項" + }, "rememberThisDevice": { "message": "記住這個裝置" }, @@ -3259,25 +3241,25 @@ "message": "需要組織 SSO 識別碼。" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "建立帳號於" }, "checkYourEmail": { - "message": "Check your email" + "message": "檢查您的電子郵件" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "跟隨電子郵件中的連結" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "並繼續建立您的帳號" }, "noEmail": { - "message": "No email?" + "message": "沒有電子郵件?" }, "goBack": { - "message": "Go back" + "message": "返回" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "來編輯您的電子郵件位址。" }, "eu": { "message": "歐盟", @@ -3313,15 +3295,18 @@ "userEmailMissing": { "message": "缺少使用者電子郵件地址" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "未找到使用中帳號的電子郵件。正在將您登出。" + }, "deviceTrusted": { "message": "裝置已信任" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "沒有可用的 Send", "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.", + "message": "使用 Send 可以與任何人安全地共用加密資訊。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3398,10 +3383,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "您需注意上方的 1 個欄位。" }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "您需注意上方的 $COUNT$ 個欄位。", "placeholders": { "count": { "content": "$1", @@ -3488,7 +3473,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": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3510,7 +3495,7 @@ "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": { @@ -3518,9 +3503,17 @@ "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": "基於時間的一次性驗證碼", + "description": "Aria label for the totp code displayed in the inline menu for autofill" + }, + "totpSecondsSpanAria": { + "message": "現在的 TOTP 到期剩餘時間", + "description": "Aria label for the totp seconds displayed in the inline menu for autofill" + }, "fillCredentialsFor": { "message": "填入登入資訊給", "description": "Screen reader text for when overlay item is in focused" @@ -3546,23 +3539,23 @@ "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": { @@ -3652,19 +3645,19 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "彈出擴充套件視窗來完成登入。" }, "popoutExtension": { - "message": "Popout extension" + "message": "彈出擴充套件視窗" }, "launchDuo": { "message": "開啟Duo" @@ -3682,7 +3675,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3741,13 +3734,16 @@ "message": "確認檔案密碼" }, "exportSuccess": { - "message": "Vault data exported" + "message": "密碼庫資料已匯出" }, "typePasskey": { "message": "密碼金鑰" }, "accessing": { - "message": "Accessing" + "message": "正在存取" + }, + "loggedInExclamation": { + "message": "已登入!" }, "passkeyNotCopied": { "message": "密碼金鑰不會被複製" @@ -3759,7 +3755,7 @@ "message": "發起站點需要驗證。沒有主密碼的帳戶尚未開通此功能。" }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "使用密碼金鑰登入?" }, "passkeyAlreadyExists": { "message": "用於這個應用程式的密碼金鑰已經存在。" @@ -3771,10 +3767,10 @@ "message": "您沒有符合該網站的登入資訊。" }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "未找到此網站的登入資訊" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "搜尋或將密碼金鑰儲存為新的登入資訊" }, "confirm": { "message": "確認" @@ -3786,10 +3782,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": "密碼金鑰項目" @@ -3910,19 +3906,19 @@ "message": "伺服器" }, "hostedAt": { - "message": "hosted at" + "message": "架設在" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "使用您的裝置或密碼金鑰" }, "justOnce": { "message": "僅此一次" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "永遠針對此網站" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ 已新增到排除的網域。", "placeholders": { "domain": { "content": "$1", @@ -3931,51 +3927,51 @@ } }, "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": { @@ -3983,48 +3979,48 @@ "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": "自動填入建議" }, "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": "複製資訊 - $ITEMNAME$", @@ -4037,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "複製備註 - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4067,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "檢視項目 - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4077,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "自動填入 - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4087,22 +4083,22 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "沒有資料可以複製" }, "assignToCollections": { "message": "指派至集合" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copyPhone": { - "message": "Copy phone" + "message": "複製電話" }, "copyAddress": { - "message": "Copy address" + "message": "複製地址" }, "adminConsole": { - "message": "Admin Console" + "message": "管理控制台" }, "accountSecurity": { "message": "帳戶安全性" @@ -4114,13 +4110,13 @@ "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": { @@ -4130,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4140,7 +4136,7 @@ } }, "new": { - "message": "New" + "message": "新增" }, "removeItem": { "message": "移除 $NAME$", @@ -4153,16 +4149,16 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "不在任何資料夾中的項目" }, "itemDetails": { - "message": "Item details" + "message": "項目詳細資訊" }, "itemName": { - "message": "Item name" + "message": "項目名稱" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "若您只有檢視權限,無法移除集合 $COLLECTIONS$。", "placeholders": { "collections": { "content": "$1", @@ -4171,47 +4167,47 @@ } }, "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": "最大檔案大小為 500MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "刪除附檔 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4220,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "下載 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4229,28 +4225,43 @@ } }, "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": "過濾密碼庫" + }, + "filterApplied": { + "message": "套用了一個過濾條件" + }, + "filterAppliedPlural": { + "message": "套用了 $COUNT$ 個過濾條件", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } }, "personalDetails": { - "message": "Personal details" + "message": "個人資訊" }, "identification": { - "message": "Identification" + "message": "身份" }, "contactInfo": { - "message": "Contact info" + "message": "聯繫資訊" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "下載 - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4259,23 +4270,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": "自動填入選項" }, "websiteUri": { - "message": "Website (URI)" + "message": "網站 (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "網站 (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4285,16 +4296,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": { @@ -4304,7 +4315,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "顯示偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4313,7 +4324,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "隱藏偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4322,19 +4333,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", @@ -4352,34 +4363,34 @@ "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", @@ -4391,37 +4402,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 id、名稱、標籤或預留字元" }, "editField": { - "message": "Edit field" + "message": "編輯欄位" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "編輯 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "刪除 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ 已新增", "placeholders": { "label": { "content": "$1", @@ -4448,7 +4459,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "重新排序 $LABEL$。使用方向鍵來往上或下移動。", "placeholders": { "label": { "content": "$1", @@ -4457,7 +4468,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "往上移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4477,10 +4488,10 @@ "message": "選擇要指派的集合" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 個項目會被永久移到選擇的組織。您將不再擁有此項目。" }, "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", @@ -4489,7 +4500,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 個項目會被永久移到 $ORG$。您將不再擁有此項目。", "placeholders": { "org": { "content": "$1", @@ -4498,7 +4509,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", @@ -4514,10 +4525,10 @@ "message": "指派集合成功" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "您沒有選擇任何項目。" }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "將已選取項目移動至 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4537,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4535,7 +4546,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4544,7 +4555,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "往下移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4561,231 +4572,336 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "項目位置" }, "fileSend": { - "message": "File Send" + "message": "檔案 Send" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSend": { - "message": "Text Send" + "message": "文字 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden 有了新外觀!" }, "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "更容易使用的自動填入及密碼庫搜尋體驗。試試看吧!" }, "accountActions": { - "message": "Account actions" + "message": "帳號動作" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "在擴充套件圖示上顯示自動填入建議的數量" + }, + "showQuickCopyActions": { + "message": "在密碼庫中顯示快速複製" }, "systemDefault": { - "message": "System default" + "message": "系統預設值" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "企業政策已套用至您的選項中" + }, + "sshPrivateKey": { + "message": "私密金鑰" + }, + "sshPublicKey": { + "message": "公共金鑰" + }, + "sshFingerprint": { + "message": "指紋" + }, + "sshKeyAlgorithm": { + "message": "金鑰類型" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "重試" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "自訂逾時時間最小為 1 分鐘。" }, "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": "您刪除的項目會在此顯示,並會在 30 天之後永久刪除" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "垃圾桶中超過 30 天的項目將會被自動刪除。" }, "restore": { - "message": "Restore" + "message": "還原" }, "deleteForever": { - "message": "Delete forever" + "message": "永遠刪除" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "你沒有權限編輯這個項目" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "由於 Bitwarden 桌面應用程式已關閉,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "由於未 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "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?", + "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": "緊湊模式" + }, + "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": "擴充套件寬度" + }, + "wide": { + "message": "寬度" + }, + "extraWide": { + "message": "更寬" } } 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 f0723d75ff8..0152cd1c7ff 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 @@ -1,182 +1,78 @@ - - - - - - - - + + + + + + + - - - -
- -
+ + + +
+ +
- - -

{{ "availableAccounts" | i18n }}

-
+ + +

{{ "availableAccounts" | i18n }}

+
-
- -
-
+
+ +
+
- -

- {{ "accountLimitReached" | i18n }} -

-
-
+

+ {{ "accountLimitReached" | i18n }} +

+
+
-
- - -

- {{ "options" | i18n }} -

-
+
+ + +

+ {{ "options" | i18n }} +

+
- - - - - - - - - -
-
- - - - - -
- -
-
{{ "switchAccounts" | i18n }}
-
- -
- -
-
-
-
-
    - -
  • - -
  • - -
    - {{ "availableAccounts" | i18n }} -
    -
  • - -
  • -
    -
    -
- -

+

- -
-
{{ "options" | 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 fb636ecaf6d..25e1b2ae83f 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 @@ -10,9 +10,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { UserId } from "@bitwarden/common/types/guid"; import { AvatarModule, @@ -25,7 +23,6 @@ import { import { enableAccountSwitching } from "../../../platform/flags"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { HeaderComponent } from "../../../platform/popup/header.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -44,7 +41,6 @@ import { AccountSwitcherService } from "./services/account-switcher.service"; AvatarModule, PopupPageComponent, PopupHeaderComponent, - HeaderComponent, PopOutComponent, CurrentAccountComponent, AccountComponent, @@ -58,7 +54,6 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { loading = false; activeUserCanLock = false; - extensionRefreshFlag = false; enableAccountSwitching = true; constructor( @@ -70,7 +65,6 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { private router: Router, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private authService: AuthService, - private configService: ConfigService, private lockService: LockService, ) {} @@ -109,9 +103,6 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { async ngOnInit() { this.enableAccountSwitching = enableAccountSwitching(); - this.extensionRefreshFlag = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); const availableVaultTimeoutActions = await firstValueFrom( this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), 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 d062c67a2e3..d2e15d31899 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -1,109 +1,44 @@ - - - - - - - - - - - - + - - + 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 d54d6fe0e29..dad74977d34 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, Location } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; @@ -6,6 +8,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AvatarModule, ItemModule } from "@bitwarden/components"; +import { BiometricsService } from "@bitwarden/key-management"; import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service"; @@ -17,7 +20,6 @@ import { AccountSwitcherService, AvailableAccount } from "./services/account-swi }) export class AccountComponent { @Input() account: AvailableAccount; - @Input() extensionRefreshFlag: boolean = false; @Output() loading = new EventEmitter(); constructor( @@ -25,6 +27,7 @@ export class AccountComponent { private location: Location, private i18nService: I18nService, private logService: LogService, + private biometricsService: BiometricsService, ) {} get specialAccountAddId() { @@ -44,6 +47,9 @@ export class AccountComponent { // locked or logged out account statuses are handled by background and app.component if (result?.status === AuthenticationStatus.Unlocked) { this.location.back(); + await this.biometricsService.setShouldAutopromptNow(false); + } else { + await this.biometricsService.setShouldAutopromptNow(true); } this.loading.emit(false); } 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 12210b2b452..ea41a627848 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, Location } from "@angular/common"; import { Component } from "@angular/core"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts index 7cc9f8a92f2..535df3ec6bb 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Observable, diff --git a/apps/browser/src/auth/popup/environment.component.ts b/apps/browser/src/auth/popup/environment.component.ts index b84f03b5fd7..c922c61b7e8 100644 --- a/apps/browser/src/auth/popup/environment.component.ts +++ b/apps/browser/src/auth/popup/environment.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; 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 4a206b36fa8..8893697da17 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 @@ -1,4 +1,4 @@ - + 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 0301a76431d..10ef65d0654 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router"; @@ -23,6 +25,7 @@ export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData { showAcctSwitcher?: boolean; showBackButton?: boolean; showLogo?: boolean; + hideFooter?: boolean; } @Component({ @@ -52,6 +55,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { protected showReadonlyHostname: boolean; protected maxWidth: "md" | "3xl"; protected hasLoggedInAccount: boolean = false; + protected hideFooter: boolean; protected theme: string; protected logo = ExtensionBitwardenLogo; @@ -110,6 +114,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { this.pageIcon = firstChildRouteData["pageIcon"]; } + this.hideFooter = Boolean(firstChildRouteData["hideFooter"]); this.showReadonlyHostname = Boolean(firstChildRouteData["showReadonlyHostname"]); this.maxWidth = firstChildRouteData["maxWidth"]; @@ -156,6 +161,10 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { this.pageIcon = data.pageIcon !== null ? data.pageIcon : null; } + if (data.hideFooter !== undefined) { + this.hideFooter = data.hideFooter !== null ? data.hideFooter : null; + } + if (data.showReadonlyHostname !== undefined) { this.showReadonlyHostname = data.showReadonlyHostname; } @@ -192,6 +201,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { this.showBackButton = null; this.showLogo = null; this.maxWidth = null; + this.hideFooter = null; } ngOnDestroy() { 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 ad7e6f67361..841eefda0ad 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 @@ -25,6 +25,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, I18nMockService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon"; import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; import { AccountSwitcherService } from "../account-switching/services/account-switcher.service"; diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index 4d185fcbfc6..6060ac77abe 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/apps/browser/src/auth/popup/lock.component.html b/apps/browser/src/auth/popup/lock.component.html deleted file mode 100644 index fb1b09de49c..00000000000 --- a/apps/browser/src/auth/popup/lock.component.html +++ /dev/null @@ -1,100 +0,0 @@ -
- -
-

- {{ "verifyIdentity" | i18n }} -

-
- -
-
-
- -
-
-
-
- - -
-
- - -
-
- -
-
-
- -
-
- -
-

- -

- {{ biometricError }} -

- {{ "awaitDesktop" | i18n }} -

- - -
-
-
diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts deleted file mode 100644 index c7fb108de80..00000000000 --- a/apps/browser/src/auth/popup/lock.component.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Component, NgZone, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.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 { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { 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 { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; - -import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors"; -import { BrowserRouterService } from "../../platform/popup/services/browser-router.service"; -import { fido2PopoutSessionData$ } from "../../vault/popup/utils/fido2-popout-session-data"; - -@Component({ - selector: "app-lock", - templateUrl: "lock.component.html", -}) -export class LockComponent extends BaseLockComponent implements OnInit { - private isInitialLockScreen: boolean; - - biometricError: string; - pendingBiometric = false; - fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - masterPasswordService: InternalMasterPasswordServiceAbstraction, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - messagingService: MessagingService, - keyService: KeyService, - vaultTimeoutService: VaultTimeoutService, - vaultTimeoutSettingsService: VaultTimeoutSettingsService, - environmentService: EnvironmentService, - stateService: StateService, - apiService: ApiService, - logService: LogService, - ngZone: NgZone, - policyApiService: PolicyApiServiceAbstraction, - policyService: InternalPolicyService, - passwordStrengthService: PasswordStrengthServiceAbstraction, - authService: AuthService, - dialogService: DialogService, - deviceTrustService: DeviceTrustServiceAbstraction, - userVerificationService: UserVerificationService, - pinService: PinServiceAbstraction, - private routerService: BrowserRouterService, - biometricStateService: BiometricStateService, - biometricsService: BiometricsService, - accountService: AccountService, - kdfConfigService: KdfConfigService, - syncService: SyncService, - toastService: ToastService, - ) { - super( - masterPasswordService, - router, - i18nService, - platformUtilsService, - messagingService, - keyService, - vaultTimeoutService, - vaultTimeoutSettingsService, - environmentService, - stateService, - apiService, - logService, - ngZone, - policyApiService, - policyService, - passwordStrengthService, - dialogService, - deviceTrustService, - userVerificationService, - pinService, - biometricStateService, - biometricsService, - accountService, - authService, - kdfConfigService, - syncService, - toastService, - ); - this.successRoute = "/tabs/current"; - this.isInitialLockScreen = (window as any).previousPopupUrl == null; - - this.onSuccessfulSubmit = async () => { - const previousUrl = this.routerService.getPreviousUrl(); - if (previousUrl) { - // 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.navigateByUrl(previousUrl); - } else { - // 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([this.successRoute]); - } - }; - } - - async ngOnInit() { - await super.ngOnInit(); - const autoBiometricsPrompt = await firstValueFrom( - this.biometricStateService.promptAutomatically$, - ); - - window.setTimeout(async () => { - document.getElementById(this.pinEnabled ? "pin" : "masterPassword")?.focus(); - if ( - this.biometricLock && - autoBiometricsPrompt && - this.isInitialLockScreen && - (await this.authService.getAuthStatus()) === AuthenticationStatus.Locked - ) { - await this.unlockBiometric(true); - } - }, 100); - } - - override async unlockBiometric(automaticPrompt: boolean = false): Promise { - if (!this.biometricLock) { - return; - } - - this.biometricError = null; - - let success; - try { - const available = await super.isBiometricUnlockAvailable(); - if (!available) { - if (!automaticPrompt) { - await this.dialogService.openSimpleDialog({ - type: "warning", - title: { key: "biometricsNotAvailableTitle" }, - content: { key: "biometricsNotAvailableDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - }); - } - } else { - this.pendingBiometric = true; - success = await super.unlockBiometric(); - } - } catch (e) { - const error = BiometricErrors[e?.message as BiometricErrorTypes]; - - if (error == null) { - this.logService.error("Unknown error: " + e); - return false; - } - - this.biometricError = this.i18nService.t(error.description); - } finally { - this.pendingBiometric = false; - } - - return success; - } -} diff --git a/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.spec.ts b/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.spec.ts new file mode 100644 index 00000000000..8f3199cdfce --- /dev/null +++ b/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.spec.ts @@ -0,0 +1,64 @@ +import { Router } from "@angular/router"; +import { MockProxy, mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; + +import { postLogoutMessageListener$ } from "../utils/post-logout-message-listener"; + +import { ExtensionLoginDecryptionOptionsService } from "./extension-login-decryption-options.service"; + +// Mock the module providing postLogoutMessageListener$ +jest.mock("../utils/post-logout-message-listener", () => { + return { + postLogoutMessageListener$: new BehaviorSubject(""), // Replace with mock subject + }; +}); + +describe("ExtensionLoginDecryptionOptionsService", () => { + let service: ExtensionLoginDecryptionOptionsService; + + let messagingService: MockProxy; + let router: MockProxy; + let postLogoutMessageSubject: BehaviorSubject; + + beforeEach(() => { + messagingService = mock(); + router = mock(); + + // Cast postLogoutMessageListener$ to BehaviorSubject for dynamic control + postLogoutMessageSubject = postLogoutMessageListener$ as BehaviorSubject; + + service = new ExtensionLoginDecryptionOptionsService(messagingService, router); + }); + + it("should instantiate the service", () => { + expect(service).not.toBeFalsy(); + }); + + describe("logOut()", () => { + it("should send a logout message", async () => { + postLogoutMessageSubject.next("switchAccountFinish"); + + await service.logOut(); + + expect(messagingService.send).toHaveBeenCalledWith("logout"); + }); + + it("should navigate to root on 'switchAccountFinish'", async () => { + postLogoutMessageSubject.next("switchAccountFinish"); + + await service.logOut(); + + expect(router.navigate).toHaveBeenCalledWith(["/"]); + }); + + it("should not navigate for 'doneLoggingOut'", async () => { + postLogoutMessageSubject.next("doneLoggingOut"); + + await service.logOut(); + + expect(router.navigate).not.toHaveBeenCalled(); + }); + }); +}); 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 new file mode 100644 index 00000000000..ea529e277e6 --- /dev/null +++ b/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.ts @@ -0,0 +1,37 @@ +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { + DefaultLoginDecryptionOptionsService, + LoginDecryptionOptionsService, +} from "@bitwarden/auth/angular"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; + +import { postLogoutMessageListener$ } from "../utils/post-logout-message-listener"; + +export class ExtensionLoginDecryptionOptionsService + extends DefaultLoginDecryptionOptionsService + implements LoginDecryptionOptionsService +{ + constructor( + protected messagingService: MessagingService, + private router: Router, + ) { + super(messagingService); + } + + override async logOut(): Promise { + // start listening for "switchAccountFinish" or "doneLoggingOut" + const messagePromise = firstValueFrom(postLogoutMessageListener$); + + super.logOut(); + + // wait for messages + const command = await messagePromise; + + // doneLoggingOut already has a message handler that will navigate us + if (command === "switchAccountFinish") { + await this.router.navigate(["/"]); + } + } +} diff --git a/apps/browser/src/auth/popup/login-decryption-options/login-decryption-options.component.html b/apps/browser/src/auth/popup/login-decryption-options/login-decryption-options-v1.component.html similarity index 100% rename from apps/browser/src/auth/popup/login-decryption-options/login-decryption-options.component.html rename to apps/browser/src/auth/popup/login-decryption-options/login-decryption-options-v1.component.html diff --git a/apps/browser/src/auth/popup/login-decryption-options/login-decryption-options.component.ts b/apps/browser/src/auth/popup/login-decryption-options/login-decryption-options-v1.component.ts similarity index 80% rename from apps/browser/src/auth/popup/login-decryption-options/login-decryption-options.component.ts rename to apps/browser/src/auth/popup/login-decryption-options/login-decryption-options-v1.component.ts index 6231b027749..bd8f808c910 100644 --- a/apps/browser/src/auth/popup/login-decryption-options/login-decryption-options.component.ts +++ b/apps/browser/src/auth/popup/login-decryption-options/login-decryption-options-v1.component.ts @@ -1,15 +1,15 @@ import { Component } from "@angular/core"; import { firstValueFrom } from "rxjs"; -import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component"; +import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component"; import { postLogoutMessageListener$ } from "../utils/post-logout-message-listener"; @Component({ selector: "browser-login-decryption-options", - templateUrl: "login-decryption-options.component.html", + templateUrl: "login-decryption-options-v1.component.html", }) -export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent { +export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 { override async createUser(): Promise { try { await super.createUser(); diff --git a/apps/browser/src/auth/popup/login-v1.component.html b/apps/browser/src/auth/popup/login-v1.component.html index 9d2b4fccad7..145a9cbc754 100644 --- a/apps/browser/src/auth/popup/login-v1.component.html +++ b/apps/browser/src/auth/popup/login-v1.component.html @@ -64,7 +64,7 @@

> -
+
diff --git a/apps/browser/src/auth/popup/login-v1.component.ts b/apps/browser/src/auth/popup/login-v1.component.ts index eee1bcc4d3f..cb6380d62ed 100644 --- a/apps/browser/src/auth/popup/login-v1.component.ts +++ b/apps/browser/src/auth/popup/login-v1.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, NgZone, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; @@ -25,14 +27,11 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv import { ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { flagEnabled } from "../../platform/flags"; - @Component({ selector: "app-login", templateUrl: "login-v1.component.html", }) export class LoginComponentV1 extends BaseLoginComponent implements OnInit { - showPasswordless = false; constructor( devicesApiService: DevicesApiServiceAbstraction, appIdService: AppIdService, @@ -82,14 +81,11 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { await syncService.fullSync(true); }; this.successRoute = "/tabs/vault"; - this.showPasswordless = flagEnabled("showPasswordless"); } async ngOnInit(): Promise { await super.ngOnInit(); - if (this.showPasswordless) { - await this.validateEmail(); - } + await this.validateEmail(); } settings() { diff --git a/apps/browser/src/auth/popup/login-via-auth-request.component.html b/apps/browser/src/auth/popup/login-via-auth-request-v1.component.html similarity index 94% rename from apps/browser/src/auth/popup/login-via-auth-request.component.html rename to apps/browser/src/auth/popup/login-via-auth-request-v1.component.html index 2abff7bdb9c..e7fafbb252c 100644 --- a/apps/browser/src/auth/popup/login-via-auth-request.component.html +++ b/apps/browser/src/auth/popup/login-via-auth-request-v1.component.html @@ -30,7 +30,7 @@

@@ -53,7 +53,7 @@

diff --git a/apps/browser/src/auth/popup/login-via-auth-request.component.ts b/apps/browser/src/auth/popup/login-via-auth-request-v1.component.ts similarity index 91% rename from apps/browser/src/auth/popup/login-via-auth-request.component.ts rename to apps/browser/src/auth/popup/login-via-auth-request-v1.component.ts index 9dc0d7d5454..66c69d0a41a 100644 --- a/apps/browser/src/auth/popup/login-via-auth-request.component.ts +++ b/apps/browser/src/auth/popup/login-via-auth-request-v1.component.ts @@ -2,7 +2,7 @@ import { Location } from "@angular/common"; import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { LoginViaAuthRequestComponent as BaseLoginWithDeviceComponent } from "@bitwarden/angular/auth/components/login-via-auth-request.component"; +import { LoginViaAuthRequestComponentV1 as BaseLoginViaAuthRequestComponentV1 } from "@bitwarden/angular/auth/components/login-via-auth-request-v1.component"; import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction, @@ -27,9 +27,9 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-login-via-auth-request", - templateUrl: "login-via-auth-request.component.html", + templateUrl: "login-via-auth-request-v1.component.html", }) -export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { +export class LoginViaAuthRequestComponentV1 extends BaseLoginViaAuthRequestComponentV1 { constructor( router: Router, keyService: KeyService, diff --git a/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts b/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts index a7e29171015..4d3a7763013 100644 --- a/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts +++ b/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts @@ -8,7 +8,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { flagEnabled } from "../../../platform/flags"; import { BrowserPlatformUtilsService } from "../../../platform/services/platform-utils/browser-platform-utils.service"; import { ExtensionAnonLayoutWrapperDataService } from "../extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; @@ -62,18 +61,6 @@ describe("ExtensionLoginComponentService", () => { expect(service).toBeTruthy(); }); - describe("isLoginViaAuthRequestSupported", () => { - it("returns true if showPasswordless flag is enabled", () => { - (flagEnabled as jest.Mock).mockReturnValue(true); - expect(service.isLoginViaAuthRequestSupported()).toBe(true); - }); - - it("returns false if showPasswordless flag is disabled", () => { - (flagEnabled as jest.Mock).mockReturnValue(false); - expect(service.isLoginViaAuthRequestSupported()).toBeFalsy(); - }); - }); - describe("showBackButton", () => { it("sets showBackButton in extensionAnonLayoutWrapperDataService", () => { service.showBackButton(true); diff --git a/apps/browser/src/auth/popup/login/extension-login-component.service.ts b/apps/browser/src/auth/popup/login/extension-login-component.service.ts index 8630030e8e2..3de5439cf2b 100644 --- a/apps/browser/src/auth/popup/login/extension-login-component.service.ts +++ b/apps/browser/src/auth/popup/login/extension-login-component.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { DefaultLoginComponentService, LoginComponentService } from "@bitwarden/auth/angular"; @@ -7,7 +9,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { flagEnabled } from "../../../platform/flags"; import { ExtensionAnonLayoutWrapperDataService } from "../extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; @Injectable() @@ -33,10 +34,6 @@ export class ExtensionLoginComponentService this.clientType = this.platformUtilsService.getClientType(); } - isLoginViaAuthRequestSupported(): boolean { - return flagEnabled("showPasswordless"); - } - showBackButton(showBackButton: boolean): void { this.extensionAnonLayoutWrapperDataService.setAnonLayoutWrapperData({ showBackButton }); } diff --git a/apps/browser/src/auth/popup/login/extension-sso-component.service.spec.ts b/apps/browser/src/auth/popup/login/extension-sso-component.service.spec.ts new file mode 100644 index 00000000000..7d64c4114c0 --- /dev/null +++ b/apps/browser/src/auth/popup/login/extension-sso-component.service.spec.ts @@ -0,0 +1,67 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { + EnvironmentService, + Environment, +} from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; + +import { BrowserApi } from "../../../platform/browser/browser-api"; + +import { ExtensionSsoComponentService } from "./extension-sso-component.service"; + +describe("ExtensionSsoComponentService", () => { + let service: ExtensionSsoComponentService; + const baseUrl = "https://vault.bitwarden.com"; + + let syncService: MockProxy; + let authService: MockProxy; + let environmentService: MockProxy; + let i18nService: MockProxy; + let logService: MockProxy; + + beforeEach(() => { + syncService = mock(); + authService = mock(); + environmentService = mock(); + i18nService = mock(); + logService = mock(); + environmentService.environment$ = new BehaviorSubject({ + getWebVaultUrl: () => baseUrl, + } as Environment); + + TestBed.configureTestingModule({ + providers: [ + { provide: SyncService, useValue: syncService }, + { provide: AuthService, useValue: authService }, + { provide: EnvironmentService, useValue: environmentService }, + { provide: I18nService, useValue: i18nService }, + { provide: LogService, useValue: logService }, + ExtensionSsoComponentService, + ], + }); + + service = TestBed.inject(ExtensionSsoComponentService); + + jest.spyOn(BrowserApi, "reloadOpenWindows").mockImplementation(); + }); + + it("creates the service", () => { + expect(service).toBeTruthy(); + }); + + describe("closeWindow", () => { + it("closes window", async () => { + const windowSpy = jest.spyOn(window, "close").mockImplementation(); + + await service.closeWindow?.(); + + expect(windowSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/auth/popup/login/extension-sso-component.service.ts b/apps/browser/src/auth/popup/login/extension-sso-component.service.ts new file mode 100644 index 00000000000..3ddc7c67f7c --- /dev/null +++ b/apps/browser/src/auth/popup/login/extension-sso-component.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from "@angular/core"; + +import { DefaultSsoComponentService, SsoComponentService } from "@bitwarden/auth/angular"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; + +/** + * This service is used to handle the SSO login process for the browser extension. + */ +@Injectable() +export class ExtensionSsoComponentService + extends DefaultSsoComponentService + implements SsoComponentService +{ + constructor( + protected syncService: SyncService, + protected authService: AuthService, + protected environmentService: EnvironmentService, + protected i18nService: I18nService, + protected logService: LogService, + ) { + super(); + } + + /** + * Closes the popup window after a successful login. + */ + async closeWindow() { + window.close(); + } +} diff --git a/apps/browser/src/auth/popup/register.component.ts b/apps/browser/src/auth/popup/register.component.ts index 7c785d1912a..f8a332f0fd1 100644 --- a/apps/browser/src/auth/popup/register.component.ts +++ b/apps/browser/src/auth/popup/register.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; diff --git a/apps/browser/src/auth/popup/settings/account-security-v1.component.html b/apps/browser/src/auth/popup/settings/account-security-v1.component.html deleted file mode 100644 index dff9675743f..00000000000 --- a/apps/browser/src/auth/popup/settings/account-security-v1.component.html +++ /dev/null @@ -1,140 +0,0 @@ - -
- -
-

- {{ "accountSecurity" | i18n }} -

-
- -
-
-
-
-

{{ "unlockMethods" | i18n }}

-
-
- - -
-
- - -
-
- - -
-
-
-
-

{{ "sessionTimeoutHeader" | i18n }}

-
- - - {{ - "vaultTimeoutPolicyWithActionInEffect" - | i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n) - }} - - - {{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }} - - - {{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }} - - - - -
- - -
- -
-
-
-

{{ "otherOptions" | i18n }}

-
- - - - - -
-
-
diff --git a/apps/browser/src/auth/popup/settings/account-security-v1.component.ts b/apps/browser/src/auth/popup/settings/account-security-v1.component.ts deleted file mode 100644 index 9d8a2ac4c88..00000000000 --- a/apps/browser/src/auth/popup/settings/account-security-v1.component.ts +++ /dev/null @@ -1,497 +0,0 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { - BehaviorSubject, - combineLatest, - concatMap, - distinctUntilChanged, - filter, - firstValueFrom, - map, - Observable, - pairwise, - startWith, - Subject, - switchMap, - takeUntil, -} from "rxjs"; - -import { FingerprintDialogComponent } from "@bitwarden/auth/angular"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { - VaultTimeout, - VaultTimeoutOption, - VaultTimeoutStringType, -} from "@bitwarden/common/types/vault-timeout.type"; -import { DialogService } from "@bitwarden/components"; -import { KeyService, BiometricStateService, BiometricsService } from "@bitwarden/key-management"; - -import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { SetPinComponent } from "../components/set-pin.component"; - -import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; - -@Component({ - selector: "auth-account-security", - templateUrl: "account-security-v1.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AccountSecurityComponent implements OnInit, OnDestroy { - protected readonly VaultTimeoutAction = VaultTimeoutAction; - - availableVaultTimeoutActions: VaultTimeoutAction[] = []; - vaultTimeoutOptions: VaultTimeoutOption[]; - vaultTimeoutPolicyCallout: Observable<{ - timeout: { hours: string; minutes: string }; - action: VaultTimeoutAction; - }>; - supportsBiometric: boolean; - showChangeMasterPass = true; - accountSwitcherEnabled = false; - - form = this.formBuilder.group({ - vaultTimeout: [null as VaultTimeout | null], - vaultTimeoutAction: [VaultTimeoutAction.Lock], - pin: [null as boolean | null], - biometric: false, - enableAutoBiometricsPrompt: true, - }); - - private refreshTimeoutSettings$ = new BehaviorSubject(undefined); - private destroy$ = new Subject(); - - constructor( - private accountService: AccountService, - private pinService: PinServiceAbstraction, - private policyService: PolicyService, - private formBuilder: FormBuilder, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - private vaultTimeoutService: VaultTimeoutService, - private vaultTimeoutSettingsService: VaultTimeoutSettingsService, - public messagingService: MessagingService, - private environmentService: EnvironmentService, - private keyService: KeyService, - private stateService: StateService, - private userVerificationService: UserVerificationService, - private dialogService: DialogService, - private changeDetectorRef: ChangeDetectorRef, - private biometricStateService: BiometricStateService, - private biometricsService: BiometricsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - const maximumVaultTimeoutPolicy = this.policyService.get$(PolicyType.MaximumVaultTimeout); - this.vaultTimeoutPolicyCallout = maximumVaultTimeoutPolicy.pipe( - filter((policy) => policy != null), - map((policy) => { - let timeout; - if (policy.data?.minutes) { - timeout = { - hours: Math.floor(policy.data?.minutes / 60).toString(), - minutes: (policy.data?.minutes % 60).toString(), - }; - } - return { timeout: timeout, action: policy.data?.action }; - }), - ); - - const showOnLocked = - !this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari(); - - this.vaultTimeoutOptions = [ - { name: this.i18nService.t("immediately"), value: 0 }, - { name: this.i18nService.t("oneMinute"), value: 1 }, - { name: this.i18nService.t("fiveMinutes"), value: 5 }, - { name: this.i18nService.t("fifteenMinutes"), value: 15 }, - { name: this.i18nService.t("thirtyMinutes"), value: 30 }, - { name: this.i18nService.t("oneHour"), value: 60 }, - { name: this.i18nService.t("fourHours"), value: 240 }, - ]; - - if (showOnLocked) { - this.vaultTimeoutOptions.push({ - name: this.i18nService.t("onLocked"), - value: VaultTimeoutStringType.OnLocked, - }); - } - - this.vaultTimeoutOptions.push({ - name: this.i18nService.t("onRestart"), - value: VaultTimeoutStringType.OnRestart, - }); - this.vaultTimeoutOptions.push({ - name: this.i18nService.t("never"), - value: VaultTimeoutStringType.Never, - }); - - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - let timeout = await firstValueFrom( - this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(activeAccount.id), - ); - if (timeout === VaultTimeoutStringType.OnLocked && !showOnLocked) { - timeout = VaultTimeoutStringType.OnRestart; - } - - const initialValues = { - vaultTimeout: timeout, - vaultTimeoutAction: await firstValueFrom( - this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), - ), - pin: await this.pinService.isPinSet(activeAccount.id), - biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(), - enableAutoBiometricsPrompt: await firstValueFrom( - this.biometricStateService.promptAutomatically$, - ), - }; - this.form.patchValue(initialValues, { emitEvent: false }); - - this.supportsBiometric = await this.biometricsService.supportsBiometric(); - this.showChangeMasterPass = await this.userVerificationService.hasMasterPassword(); - - this.form.controls.vaultTimeout.valueChanges - .pipe( - startWith(initialValues.vaultTimeout), // emit to init pairwise - pairwise(), - concatMap(async ([previousValue, newValue]) => { - await this.saveVaultTimeout(previousValue, newValue); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.form.controls.vaultTimeoutAction.valueChanges - .pipe( - startWith(initialValues.vaultTimeoutAction), // emit to init pairwise - pairwise(), - concatMap(async ([previousValue, newValue]) => { - await this.saveVaultTimeoutAction(previousValue, newValue); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.form.controls.pin.valueChanges - .pipe( - concatMap(async (value) => { - await this.updatePin(value); - this.refreshTimeoutSettings$.next(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.form.controls.biometric.valueChanges - .pipe( - distinctUntilChanged(), - concatMap(async (enabled) => { - await this.updateBiometric(enabled); - if (enabled) { - this.form.controls.enableAutoBiometricsPrompt.enable(); - } else { - this.form.controls.enableAutoBiometricsPrompt.disable(); - } - this.refreshTimeoutSettings$.next(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.refreshTimeoutSettings$ - .pipe( - switchMap(() => - combineLatest([ - this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), - this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe(([availableActions, action]) => { - this.availableVaultTimeoutActions = availableActions; - this.form.controls.vaultTimeoutAction.setValue(action, { emitEvent: false }); - // NOTE: The UI doesn't properly update without detect changes. - // I've even tried using an async pipe, but it still doesn't work. I'm not sure why. - // Using an async pipe means that we can't call `detectChanges` AFTER the data has change - // meaning that we are forced to use regular class variables instead of observables. - this.changeDetectorRef.detectChanges(); - }); - - this.refreshTimeoutSettings$ - .pipe( - switchMap(() => - combineLatest([ - this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), - maximumVaultTimeoutPolicy, - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe(([availableActions, policy]) => { - if (policy?.data?.action || availableActions.length <= 1) { - this.form.controls.vaultTimeoutAction.disable({ emitEvent: false }); - } else { - this.form.controls.vaultTimeoutAction.enable({ emitEvent: false }); - } - }); - } - - async saveVaultTimeout(previousValue: VaultTimeout, newValue: VaultTimeout) { - if (newValue === VaultTimeoutStringType.Never) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "warning" }, - content: { key: "neverLockWarning" }, - type: "warning", - }); - - if (!confirmed) { - this.form.controls.vaultTimeout.setValue(previousValue, { emitEvent: false }); - return; - } - } - - // The minTimeoutError does not apply to browser because it supports Immediately - // So only check for the policyError - if (this.form.controls.vaultTimeout.hasError("policyError")) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("vaultTimeoutTooLarge"), - ); - return; - } - - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - const vaultTimeoutAction = await firstValueFrom( - this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), - ); - - await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( - activeAccount.id, - newValue, - vaultTimeoutAction, - ); - if (newValue === VaultTimeoutStringType.Never) { - this.messagingService.send("bgReseedStorage"); - } - } - - async saveVaultTimeoutAction(previousValue: VaultTimeoutAction, newValue: VaultTimeoutAction) { - if (newValue === VaultTimeoutAction.LogOut) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "vaultTimeoutLogOutConfirmationTitle" }, - content: { key: "vaultTimeoutLogOutConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - this.form.controls.vaultTimeoutAction.setValue(previousValue, { - emitEvent: false, - }); - return; - } - } - - if (this.form.controls.vaultTimeout.hasError("policyError")) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("vaultTimeoutTooLarge"), - ); - return; - } - - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( - activeAccount.id, - this.form.value.vaultTimeout, - newValue, - ); - this.refreshTimeoutSettings$.next(); - } - - async updatePin(value: boolean) { - if (value) { - const dialogRef = SetPinComponent.open(this.dialogService); - - if (dialogRef == null) { - this.form.controls.pin.setValue(false, { emitEvent: false }); - return; - } - - const userHasPinSet = await firstValueFrom(dialogRef.closed); - this.form.controls.pin.setValue(userHasPinSet, { emitEvent: false }); - } else { - await this.vaultTimeoutSettingsService.clear(); - } - } - - async updateBiometric(enabled: boolean) { - if (enabled && this.supportsBiometric) { - let granted; - try { - granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] }); - } catch (e) { - // eslint-disable-next-line - console.error(e); - - if (this.platformUtilsService.isFirefox() && BrowserPopupUtils.inSidebar(window)) { - await this.dialogService.openSimpleDialog({ - title: { key: "nativeMessaginPermissionSidebarTitle" }, - content: { key: "nativeMessaginPermissionSidebarDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "info", - }); - - this.form.controls.biometric.setValue(false); - return; - } - } - - if (!granted) { - await this.dialogService.openSimpleDialog({ - title: { key: "nativeMessaginPermissionErrorTitle" }, - content: { key: "nativeMessaginPermissionErrorDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - - this.form.controls.biometric.setValue(false); - return; - } - - const awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService); - const awaitDesktopDialogClosed = firstValueFrom(awaitDesktopDialogRef.closed); - - await this.keyService.refreshAdditionalKeys(); - - await Promise.race([ - awaitDesktopDialogClosed.then(async (result) => { - if (result !== true) { - this.form.controls.biometric.setValue(false); - } - }), - this.biometricsService - .authenticateBiometric() - .then((result) => { - this.form.controls.biometric.setValue(result); - if (!result) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorEnableBiometricTitle"), - this.i18nService.t("errorEnableBiometricDesc"), - ); - } - }) - .catch((e) => { - // Handle connection errors - this.form.controls.biometric.setValue(false); - - const error = BiometricErrors[e.message as BiometricErrorTypes]; - - // 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.dialogService.openSimpleDialog({ - title: { key: error.title }, - content: { key: error.description }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - }) - .finally(() => { - awaitDesktopDialogRef.close(true); - }), - ]); - } else { - await this.biometricStateService.setBiometricUnlockEnabled(false); - await this.biometricStateService.setFingerprintValidated(false); - } - } - - async updateAutoBiometricsPrompt() { - await this.biometricStateService.setPromptAutomatically( - this.form.value.enableAutoBiometricsPrompt, - ); - } - - async changePassword() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToWebApp" }, - content: { key: "changeMasterPasswordOnWebConfirmation" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const env = await firstValueFrom(this.environmentService.environment$); - await BrowserApi.createNewTab(env.getWebVaultUrl()); - } - } - - async twoStep() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "twoStepLogin" }, - content: { key: "twoStepLoginConfirmation" }, - type: "info", - }); - if (confirmed) { - // 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 - BrowserApi.createNewTab("https://bitwarden.com/help/setup-two-step-login/"); - } - } - - async fingerprint() { - const fingerprint = await this.keyService.getFingerprint(await this.stateService.getUserId()); - - const dialogRef = FingerprintDialogComponent.open(this.dialogService, { - fingerprint, - }); - - return firstValueFrom(dialogRef.closed); - } - - async lock() { - await this.vaultTimeoutService.lock(); - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - type: "info", - }); - - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - if (confirmed) { - this.messagingService.send("logout", { userId: userId }); - } - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } -} 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 00e1fd17150..0f2754b2bf2 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -11,13 +11,16 @@

{{ "unlockMethods" | i18n }}

- + {{ "unlockWithBiometrics" | i18n }} + + {{ biometricUnavailabilityReason }} + - + {{ "vaultTimeoutHeader" | i18n }}

{{ "vaultTimeoutAction1" | i18n }} - + + + + {{ "vaultTimeoutActionDesc" | i18n }} @@ -109,18 +109,6 @@

{{ "otherOptions" | i18n }}

- - - {{ "lockNow" | i18n }} - - - - {{ "logOut" | i18n }} -
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 1617ed84767..7e094fe508b 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; @@ -15,6 +17,7 @@ import { Subject, switchMap, takeUntil, + timer, } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -51,14 +54,17 @@ import { TypographyModule, ToastService, } from "@bitwarden/components"; -import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; +import { + KeyService, + BiometricsService, + BiometricStateService, + BiometricsStatus, +} from "@bitwarden/key-management"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; 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"; import { SetPinComponent } from "../components/set-pin.component"; @@ -80,7 +86,6 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; JslibModule, LinkModule, PopOutComponent, - PopupFooterComponent, PopupHeaderComponent, PopupPageComponent, RouterModule, @@ -99,9 +104,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { availableVaultTimeoutActions: VaultTimeoutAction[] = []; vaultTimeoutOptions: VaultTimeoutOption[] = []; hasVaultTimeoutPolicy = false; - supportsBiometric: boolean; + biometricUnavailabilityReason: string; showChangeMasterPass = true; - accountSwitcherEnabled = false; form = this.formBuilder.group({ vaultTimeout: [null as VaultTimeout | null], @@ -134,9 +138,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private toastService: ToastService, private biometricsService: BiometricsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } + ) {} async ngOnInit() { const hasMasterPassword = await this.userVerificationService.hasMasterPassword(); @@ -199,7 +201,41 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { }; this.form.patchValue(initialValues, { emitEvent: false }); - this.supportsBiometric = await this.biometricsService.supportsBiometric(); + timer(0, 1000) + .pipe( + switchMap(async () => { + const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id); + const biometricSettingAvailable = + !(await BrowserApi.permissionsGranted(["nativeMessaging"])) || + (status !== BiometricsStatus.DesktopDisconnected && + status !== BiometricsStatus.NotEnabledInConnectedDesktopApp) || + (await this.vaultTimeoutSettingsService.isBiometricLockSet()); + if (!biometricSettingAvailable) { + this.form.controls.biometric.disable({ emitEvent: false }); + } else { + this.form.controls.biometric.enable({ emitEvent: false }); + } + + if (status === BiometricsStatus.DesktopDisconnected && !biometricSettingAvailable) { + this.biometricUnavailabilityReason = this.i18nService.t( + "biometricsStatusHelptextDesktopDisconnected", + ); + } else if ( + status === BiometricsStatus.NotEnabledInConnectedDesktopApp && + !biometricSettingAvailable + ) { + this.biometricUnavailabilityReason = this.i18nService.t( + "biometricsStatusHelptextNotEnabledInDesktop", + activeAccount.email, + ); + } else { + this.biometricUnavailabilityReason = ""; + } + }), + takeUntil(this.destroy$), + ) + .subscribe(); + this.showChangeMasterPass = await this.userVerificationService.hasMasterPassword(); this.form.controls.vaultTimeout.valueChanges @@ -215,7 +251,6 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { this.form.controls.vaultTimeoutAction.valueChanges .pipe( - startWith(initialValues.vaultTimeoutAction), // emit to init pairwise map(async (value) => { await this.saveVaultTimeoutAction(value); }), @@ -400,7 +435,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { } async updateBiometric(enabled: boolean) { - if (enabled && this.supportsBiometric) { + if (enabled) { let granted; try { granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] }); @@ -439,10 +474,13 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const successful = await this.trySetupBiometrics(); this.form.controls.biometric.setValue(successful); + await this.biometricStateService.setBiometricUnlockEnabled(successful); if (!successful) { - await this.biometricStateService.setBiometricUnlockEnabled(false); await this.biometricStateService.setFingerprintValidated(false); } + } else { + await this.biometricStateService.setBiometricUnlockEnabled(false); + await this.biometricStateService.setFingerprintValidated(false); } } @@ -469,7 +507,18 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const biometricsPromise = async () => { try { - const result = await this.biometricsService.authenticateBiometric(); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a.id)), + ); + let result = false; + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); + result = await this.keyService.validateUserKey(userKey, userId); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + result = false; + } // prevent duplicate dialog biometricsResponseReceived = true; diff --git a/apps/browser/src/auth/popup/sso.component.html b/apps/browser/src/auth/popup/sso-v1.component.html similarity index 100% rename from apps/browser/src/auth/popup/sso.component.html rename to apps/browser/src/auth/popup/sso-v1.component.html diff --git a/apps/browser/src/auth/popup/sso.component.ts b/apps/browser/src/auth/popup/sso-v1.component.ts similarity index 97% rename from apps/browser/src/auth/popup/sso.component.ts rename to apps/browser/src/auth/popup/sso-v1.component.ts index 988563c2fe6..ecb743848c7 100644 --- a/apps/browser/src/auth/popup/sso.component.ts +++ b/apps/browser/src/auth/popup/sso-v1.component.ts @@ -29,9 +29,9 @@ import { BrowserApi } from "../../platform/browser/browser-api"; @Component({ selector: "app-sso", - templateUrl: "sso.component.html", + templateUrl: "sso-v1.component.html", }) -export class SsoComponent extends BaseSsoComponent { +export class SsoComponentV1 extends BaseSsoComponent { constructor( ssoLoginService: SsoLoginServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction, diff --git a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts index 5bc9f01fc99..53aedc7a5f3 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; @@ -11,11 +13,23 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; diff --git a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts index b6211bba05f..723152adfab 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts @@ -6,12 +6,26 @@ import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { TwoFactorAuthEmailComponent as TwoFactorAuthEmailBaseComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-email.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DialogService } from "../../../../../libs/components/src/dialog"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/auth/popup/two-factor-auth.component.ts b/apps/browser/src/auth/popup/two-factor-auth.component.ts index 9e755746e6f..f22bbbe202c 100644 --- a/apps/browser/src/auth/popup/two-factor-auth.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; @@ -35,6 +37,8 @@ import { ToastService, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor.component.ts index 27c4604be91..a2f9cd9d0fc 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { Subject, Subscription, firstValueFrom } from "rxjs"; diff --git a/apps/browser/src/auth/popup/update-temp-password.component.html b/apps/browser/src/auth/popup/update-temp-password.component.html index 6e0cc0f4483..0ce82aa20cf 100644 --- a/apps/browser/src/auth/popup/update-temp-password.component.html +++ b/apps/browser/src/auth/popup/update-temp-password.component.html @@ -1,7 +1,7 @@
- {{ "logOut" | i18n }} +

{{ "updateMasterPassword" | i18n }} diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.ts index b3807cf7136..5a0e577807f 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 68d3f32b80f..6ad9b8e06fd 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore 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"; @@ -55,6 +57,17 @@ export type InlineMenuElementPosition = { height: number; }; +export type FieldRect = { + bottom: number; + height: number; + left: number; + right: number; + top: number; + width: number; + x: number; + y: number; +}; + export type InlineMenuPosition = { button?: InlineMenuElementPosition; list?: InlineMenuElementPosition; @@ -132,6 +145,7 @@ export type OverlayBackgroundExtensionMessage = { isFieldCurrentlyFilling?: boolean; subFrameData?: SubFrameOffsetData; focusedFieldData?: FocusedFieldData; + allFieldsRect?: any; isOpeningFullInlineMenu?: boolean; styles?: Partial; data?: LockedVaultPendingNotificationsData; @@ -158,6 +172,9 @@ export type InlineMenuCipherData = { icon: WebsiteIconData; accountCreationFieldType?: string; login?: { + totp?: string; + totpField?: boolean; + totpCodeTimeInterval?: number; username: string; passkey: { rpName: string; @@ -260,6 +277,7 @@ export type InlineMenuListPortMessageHandlers = { updateAutofillInlineMenuListHeight: ({ message, port }: PortOnMessageHandlerParams) => void; refreshGeneratedPassword: () => Promise; fillGeneratedPassword: ({ port }: PortConnectionParam) => Promise; + refreshOverlayCiphers: () => Promise; }; export interface OverlayBackground { 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 73f936bb591..a300ac08660 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 @@ -453,12 +453,16 @@ describe("AutoSubmitLoginBackground", () => { sendMockExtensionMessage({ command: "triggerAutoSubmitLogin" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); it("skips acting on messages whose command does not have a registered handler", () => { sendMockExtensionMessage({ command: "someInvalidCommand" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); 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 180a6ba5462..034938ca521 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/apps/browser/src/autofill/background/context-menus.background.ts b/apps/browser/src/autofill/background/context-menus.background.ts index 8d65e1c8c60..0db2fd59af3 100644 --- a/apps/browser/src/autofill/background/context-menus.background.ts +++ b/apps/browser/src/autofill/background/context-menus.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BrowserApi } from "../../platform/browser/browser-api"; import { ContextMenuClickedHandler } from "../browser/context-menu-clicked-handler"; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index e043dbfdd2e..37c05a55a3a 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -60,10 +60,18 @@ describe("NotificationBackground", () => { const configService = mock(); const accountService = mock(); + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + beforeEach(() => { activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Locked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; + accountService.activeAccount$ = activeAccountSubject; notificationBackground = new NotificationBackground( autofillService, cipherService, @@ -683,13 +691,6 @@ describe("NotificationBackground", () => { }); describe("saveOrUpdateCredentials", () => { - const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ - id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", - }); - let getDecryptedCipherByIdSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; let updatePasswordSpy: jest.SpyInstance; diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index e77996fe903..5c6ff3c2c8c 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -81,6 +83,8 @@ export default class NotificationBackground { getWebVaultUrlForNotification: () => this.getWebVaultUrl(), }; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private autofillService: AutofillService, private cipherService: CipherService, @@ -567,9 +571,7 @@ export default class NotificationBackground { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { @@ -609,10 +611,7 @@ export default class NotificationBackground { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(cipherView, activeUserId); try { // We've only updated the password, no need to broadcast editedCipher message @@ -645,17 +644,15 @@ export default class NotificationBackground { if (Utils.isNullOrWhitespace(folderId) || folderId === "null") { return false; } - - const folders = await firstValueFrom(this.folderService.folderViews$); + const activeUserId = await firstValueFrom(this.activeUserId$); + const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId)); return folders.some((x) => x.id === folderId); } private async getDecryptedCipherById(cipherId: string) { const cipher = await this.cipherService.get(cipherId); if (cipher != null && cipher.type === CipherType.Login) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), @@ -695,7 +692,8 @@ export default class NotificationBackground { * Returns the first value found from the folder service's folderViews$ observable. */ private async getFolderData() { - return await firstValueFrom(this.folderService.folderViews$); + const activeUserId = await firstValueFrom(this.activeUserId$); + return await firstValueFrom(this.folderService.folderViews$(activeUserId)); } private async getWebVaultUrl(): Promise { diff --git a/apps/browser/src/autofill/background/overlay-notifications.background.ts b/apps/browser/src/autofill/background/overlay-notifications.background.ts index 3472bfcc72f..ce30dd462b3 100644 --- a/apps/browser/src/autofill/background/overlay-notifications.background.ts +++ b/apps/browser/src/autofill/background/overlay-notifications.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { startWith, Subject, Subscription, switchMap, timer } from "rxjs"; import { pairwise } from "rxjs/operators"; diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 6ec3c0a9b5a..c3a6357ed05 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy, mockReset } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -14,6 +14,7 @@ import { } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService, Region, @@ -93,6 +94,7 @@ describe("OverlayBackground", () => { let logService: MockProxy; let cipherService: MockProxy; let autofillService: MockProxy; + let configService: MockProxy; let activeAccountStatusMock$: BehaviorSubject; let authService: MockProxy; let environmentMock$: BehaviorSubject; @@ -149,11 +151,13 @@ describe("OverlayBackground", () => { } beforeEach(() => { + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(true)); accountService = mockAccountServiceWith(mockUserId); fakeStateProvider = new FakeStateProvider(accountService); showFaviconsMock$ = new BehaviorSubject(true); neverDomainsMock$ = new BehaviorSubject({}); - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); domainSettingsService.showFavicons$ = showFaviconsMock$; domainSettingsService.neverDomains$ = neverDomainsMock$; logService = mock(); @@ -928,6 +932,7 @@ describe("OverlayBackground", () => { login: { username: "username-1", passkey: null, + totpField: false, }, name: "name-1", reprompt: loginCipher1.reprompt, @@ -1065,6 +1070,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, name: loginCipher1.name, reprompt: loginCipher1.reprompt, @@ -1189,6 +1195,7 @@ describe("OverlayBackground", () => { rpName: passkeyCipher.login.fido2Credentials[0].rpName, userName: passkeyCipher.login.fido2Credentials[0].userName, }, + totpField: false, }, }, { @@ -1207,6 +1214,7 @@ describe("OverlayBackground", () => { login: { username: passkeyCipher.login.username, passkey: null, + totpField: false, }, }, { @@ -1225,6 +1233,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, ], @@ -1272,6 +1281,7 @@ describe("OverlayBackground", () => { login: { username: passkeyCipher.login.username, passkey: null, + totpField: false, }, }, { @@ -1290,6 +1300,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, ], @@ -1337,6 +1348,7 @@ describe("OverlayBackground", () => { login: { username: passkeyCipher.login.username, passkey: null, + totpField: false, }, }, { @@ -1355,6 +1367,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, ], @@ -1400,6 +1413,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher1.login.username, passkey: null, + totpField: false, }, }, { @@ -1418,6 +1432,7 @@ describe("OverlayBackground", () => { login: { username: loginCipher2.login.username, passkey: null, + totpField: false, }, }, ], @@ -1908,7 +1923,17 @@ describe("OverlayBackground", () => { it("returns true if the overlay login ciphers are populated", async () => { overlayBackground["inlineMenuCiphers"] = new Map([ - ["inline-menu-cipher-0", mock({ type: CipherType.Login })], + [ + "inline-menu-cipher-0", + mock({ + type: CipherType.Login, + login: { + username: "username1", + password: "password1", + uri: "https://example.com", + }, + }), + ], ]); await overlayBackground["getInlineMenuCipherData"](); @@ -2888,6 +2913,124 @@ describe("OverlayBackground", () => { ); }); }); + describe("handles menu position when input is focused", () => { + it("sets button and menu width and position when non-multi-input totp field is focused", async () => { + const subframe = { + top: 0, + left: 0, + url: "", + frameId: 0, + }; + + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + focusedFieldRects: { + width: 49.328125, + height: 64, + top: 302.171875, + left: 1270.8125, + }, + }); + + const buttonPostion = overlayBackground["getInlineMenuButtonPosition"](subframe); + const menuPostion = overlayBackground["getInlineMenuListPosition"](subframe); + + expect(menuPostion).toEqual({ + width: "49px", + top: "366px", + left: "1271px", + }); + expect(buttonPostion).toEqual({ + width: "34px", + height: "34px", + top: "317px", + left: "1271px", + }); + }); + it("sets button and menu width and position when multi-input totp field is focused", async () => { + const subframe = { + top: 0, + left: 0, + url: "", + frameId: 0, + }; + + const totpFields = [ + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__0" }), + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__1" }), + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__2" }), + ]; + const allFieldData = [ + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__0", + rect: { + x: 1041.5, + y: 302.171875, + width: 49.328125, + height: 64, + top: 302.171875, + right: 1090.828125, + bottom: 366.171875, + left: 1041.5, + }, + }), + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__1", + rect: { + x: 1098.828125, + y: 302.171875, + width: 49.328125, + height: 64, + top: 302.171875, + right: 1148.15625, + bottom: 366.171875, + left: 1098.828125, + }, + }), + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__2", + rect: { + x: 1156.15625, + y: 302.171875, + width: 249.328125, + height: 64, + top: 302.171875, + right: 2205.484375, + bottom: 366.171875, + left: 2156.15625, + }, + }), + ]; + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + focusedFieldRects: { + width: 49.328125, + height: 64, + top: 302.171875, + left: 1270.8125, + }, + }); + + overlayBackground["allFieldData"] = allFieldData; + jest.spyOn(overlayBackground as any, "isTotpFieldForCurrentField").mockReturnValue(true); + jest.spyOn(overlayBackground as any, "getTotpFields").mockReturnValue(totpFields); + + const buttonPostion = overlayBackground["getInlineMenuButtonPosition"](subframe); + const menuPostion = overlayBackground["getInlineMenuListPosition"](subframe); + expect(menuPostion).toEqual({ + width: "1164px", + top: "366px", + left: "1042px", + }); + expect(buttonPostion).toEqual({ + width: "34px", + height: "34px", + top: "292px", + left: "2187px", + }); + }); + }); describe("triggerDelayedAutofillInlineMenuClosure message handler", () => { it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index c42d1f7e640..3d2b1ec783c 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { debounceTime, firstValueFrom, @@ -64,9 +66,11 @@ import { InlineMenuFormFieldData } from "../services/abstractions/autofill-overl import { AutofillService, PageDetail } from "../services/abstractions/autofill.service"; import { InlineMenuFieldQualificationService } from "../services/abstractions/inline-menu-field-qualifications.service"; import { + areKeyValuesNull, generateDomainMatchPatterns, generateRandomChars, isInvalidResponseStatusCode, + rectHasSize, specialCharacterToKeyMap, } from "../utils"; @@ -127,6 +131,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private currentInlineMenuCiphersCount: number = 0; private currentAddNewItemData: CurrentAddNewItemData; private focusedFieldData: FocusedFieldData; + private allFieldData: AutofillField[]; private isFieldCurrentlyFocused: boolean = false; private isFieldCurrentlyFilling: boolean = false; private isInlineMenuButtonVisible: boolean = false; @@ -202,6 +207,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { updateAutofillInlineMenuListHeight: ({ message }) => this.updateInlineMenuListHeight(message), refreshGeneratedPassword: () => this.updateGeneratedPassword(true), fillGeneratedPassword: ({ port }) => this.fillGeneratedPassword(port), + refreshOverlayCiphers: () => this.updateOverlayCiphers(false), }; constructor( @@ -362,8 +368,6 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.inlineMenuFido2Credentials.clear(); this.storeInlineMenuFido2Credentials$.next(currentTab.id); - await this.generatePassword(); - const ciphersViews = await this.getCipherViews(currentTab, updateAllCipherTypes); for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { this.inlineMenuCiphers.set(`inline-menu-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); @@ -464,7 +468,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.showPasskeysLabelsWithinInlineMenu = false; if (this.shouldShowInlineMenuAccountCreation()) { - inlineMenuCipherData = this.buildInlineMenuAccountCreationCiphers( + inlineMenuCipherData = await this.buildInlineMenuAccountCreationCiphers( inlineMenuCiphersArray, true, ); @@ -485,7 +489,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param inlineMenuCiphersArray - Array of inline menu ciphers * @param showFavicons - Identifies whether favicons should be shown */ - private buildInlineMenuAccountCreationCiphers( + private async buildInlineMenuAccountCreationCiphers( inlineMenuCiphersArray: [string, CipherView][], showFavicons: boolean, ) { @@ -497,7 +501,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (cipher.type === CipherType.Login) { accountCreationLoginCiphers.push( - this.buildCipherData({ + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons, @@ -517,7 +521,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } inlineMenuCipherData.push( - this.buildCipherData({ + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons, @@ -555,19 +559,41 @@ export class OverlayBackground implements OverlayBackgroundInterface { for (let cipherIndex = 0; cipherIndex < inlineMenuCiphersArray.length; cipherIndex++) { const [inlineMenuCipherId, cipher] = inlineMenuCiphersArray[cipherIndex]; + + switch (cipher.type) { + case CipherType.Card: + if (areKeyValuesNull(cipher.card)) { + continue; + } + break; + + case CipherType.Identity: + if (areKeyValuesNull(cipher.identity)) { + continue; + } + break; + + case CipherType.Login: + if ( + areKeyValuesNull(cipher.login, ["username", "password", "totp", "fido2Credentials"]) + ) { + continue; + } + break; + } if (!this.focusedFieldMatchesFillType(cipher.type)) { continue; } if (!passkeysEnabled || !(await this.showCipherAsPasskey(cipher, domainExclusionsSet))) { inlineMenuCipherData.push( - this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), ); continue; } passkeyCipherData.push( - this.buildCipherData({ + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons, @@ -577,7 +603,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (cipher.login?.password && cipher.login.username) { inlineMenuCipherData.push( - this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), + await this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), ); } } @@ -620,6 +646,23 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.inlineMenuFido2Credentials.has(credentialId); } + private isTotpFieldForCurrentField(): boolean { + if (!this.focusedFieldData) { + return false; + } + const { tabId, frameId } = this.focusedFieldData; + const pageDetailsMap = this.pageDetailsForTab[tabId]; + if (!pageDetailsMap || !pageDetailsMap.has(frameId)) { + return false; + } + const pageDetail = pageDetailsMap.get(frameId); + return ( + pageDetail?.details?.fields?.every((field) => + this.inlineMenuFieldQualificationService.isTotpField(field), + ) || false + ); + } + /** * Builds the cipher data for the inline menu list. * @@ -630,14 +673,14 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param hasPasskey - Identifies whether the cipher has a FIDO2 credential * @param identityData - Pre-created identity data */ - private buildCipherData({ + private async buildCipherData({ inlineMenuCipherId, cipher, showFavicons, showInlineMenuAccountCreation, hasPasskey, identityData, - }: BuildCipherDataParams): InlineMenuCipherData { + }: BuildCipherDataParams): Promise { const inlineMenuData: InlineMenuCipherData = { id: inlineMenuCipherId, name: cipher.name, @@ -649,8 +692,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { }; if (cipher.type === CipherType.Login) { + const totpCode = await this.totpService.getCode(cipher.login?.totp); + const totpCodeTimeInterval = this.totpService.getTimeInterval(cipher.login?.totp); inlineMenuData.login = { username: cipher.login.username, + totp: totpCode, + totpField: this.isTotpFieldForCurrentField(), + totpCodeTimeInterval: totpCodeTimeInterval, passkey: hasPasskey ? { rpName: cipher.login.fido2Credentials[0].rpName, @@ -1321,6 +1369,71 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.isInlineMenuListVisible = false; } + /** + * Get all the totp fields for the tab and frame of the currently focused field + */ + private getTotpFields(): AutofillField[] { + const currentTabId = this.focusedFieldData?.tabId; + const currentFrameId = this.focusedFieldData?.frameId; + const pageDetailsMap = this.pageDetailsForTab[currentTabId]; + const pageDetails = pageDetailsMap?.get(currentFrameId); + + const fields = pageDetails.details.fields; + const totpFields = fields.filter((f) => + this.inlineMenuFieldQualificationService.isTotpField(f), + ); + + return totpFields; + } + + /** + * calculates the postion and width for multi-input totp field inline menu + * @param totpFieldArray - the totp fields used to evaluate the position of the menu + */ + private calculateTotpMultiInputMenuBounds(totpFieldArray: AutofillField[]) { + // Filter the fields based on the provided totpfields + const filteredObjects = this.allFieldData.filter((obj) => + totpFieldArray.some((o) => o.opid === obj.opid), + ); + + // Return null if no matching objects are found + if (filteredObjects.length === 0) { + return null; + } + // Calculate the smallest left and largest right values to determine width + const left = Math.min( + ...filteredObjects.filter((obj) => rectHasSize(obj.rect)).map((obj) => obj.rect.left), + ); + const largestRight = Math.max( + ...filteredObjects.filter((obj) => rectHasSize(obj.rect)).map((obj) => obj.rect.right), + ); + + const width = largestRight - left; + + return { left, width }; + } + + /** + * calculates the postion for multi-input totp field inline button + * @param totpFieldArray - the totp fields used to evaluate the position of the menu + */ + private calculateTotpMultiInputButtonBounds(totpFieldArray: AutofillField[]) { + const filteredObjects = this.allFieldData.filter((obj) => + totpFieldArray.some((o) => o.opid === obj.opid), + ); + + if (filteredObjects.length === 0) { + return null; + } + + const maxRight = Math.max(...filteredObjects.map((obj) => obj.rect.right)); + const maxObject = filteredObjects.find((obj) => obj.rect.right === maxRight); + const top = maxObject.rect.top - maxObject.rect.height * 0.39; + const left = maxRight - maxObject.rect.height * 0.3; + + return { left, top }; + } + /** * Updates the position of either the inline menu list or button. The position * is based on the focused field's position and dimensions. @@ -1426,8 +1539,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameTopOffset = subFrameOffsets?.top || 0; const subFrameLeftOffset = subFrameOffsets?.left || 0; - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + const { width, height } = this.focusedFieldData.focusedFieldRects; + let { top, left } = this.focusedFieldData.focusedFieldRects; const { paddingRight, paddingLeft } = this.focusedFieldData.focusedFieldStyles; + + if (this.isTotpFieldForCurrentField()) { + const totpFields = this.getTotpFields(); + if (totpFields.length > 1) { + ({ left, top } = this.calculateTotpMultiInputButtonBounds(totpFields)); + } + } + let elementOffset = height * 0.37; if (height >= 35) { elementOffset = height >= 50 ? height * 0.47 : height * 0.42; @@ -1466,7 +1588,16 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameTopOffset = subFrameOffsets?.top || 0; const subFrameLeftOffset = subFrameOffsets?.left || 0; - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + const { top, height } = this.focusedFieldData.focusedFieldRects; + let { left, width } = this.focusedFieldData.focusedFieldRects; + + if (this.isTotpFieldForCurrentField()) { + const totpFields = this.getTotpFields(); + + if (totpFields.length > 1) { + ({ left, width } = this.calculateTotpMultiInputMenuBounds(totpFields)); + } + } this.inlineMenuPosition.list = { top: Math.round(top + height + subFrameTopOffset), @@ -1489,7 +1620,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param sender - The sender of the extension message */ private setFocusedFieldData( - { focusedFieldData }: OverlayBackgroundExtensionMessage, + { focusedFieldData, allFieldsRect }: OverlayBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ) { if ( @@ -1506,6 +1637,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const previousFocusedFieldData = this.focusedFieldData; this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id, frameId: sender.frameId }; + this.allFieldData = allFieldsRect; this.isFieldCurrentlyFocused = true; if (this.shouldUpdatePasswordGeneratorMenuOnFieldFocus()) { @@ -1980,35 +2112,39 @@ export class OverlayBackground implements OverlayBackgroundInterface { private getInlineMenuTranslations() { if (!this.inlineMenuPageTranslations) { const translationKeys = [ - "opensInANewWindow", - "toggleBitwardenVaultOverlay", - "unlockYourAccountToViewAutofillSuggestions", - "unlockAccount", - "unlockAccountAria", - "fillCredentialsFor", - "username", - "view", - "noItemsToShow", - "newItem", - "addNewVaultItem", - "newLogin", - "addNewLoginItemAria", - "newCard", "addNewCardItemAria", - "newIdentity", "addNewIdentityItemAria", + "addNewLoginItemAria", + "addNewVaultItem", + "authenticating", "cardNumberEndsWith", + "fillCredentialsFor", + "fillGeneratedPassword", + "fillVerificationCode", + "fillVerificationCodeAria", + "generatedPassword", + "lowercaseAriaLabel", + "logInWithPasskeyAriaLabel", + "newCard", + "newIdentity", + "newItem", + "newLogin", + "noItemsToShow", + "opensInANewWindow", "passkeys", + "passwordRegenerated", "passwords", - "logInWithPasskeyAriaLabel", - "authenticating", - "fillGeneratedPassword", "regeneratePassword", - "passwordRegenerated", "saveLoginToBitwarden", - "lowercaseAriaLabel", + "toggleBitwardenVaultOverlay", + "totpCodeAria", + "totpSecondsSpanAria", + "unlockAccount", + "unlockAccountAria", + "unlockYourAccountToViewAutofillSuggestions", "uppercaseAriaLabel", - "generatedPassword", + "username", + "view", ...Object.values(specialCharacterToKeyMap), ]; this.inlineMenuPageTranslations = translationKeys.reduce( @@ -2248,6 +2384,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { card, identity, sender, + addNewCipherType, }: CurrentAddNewItemData) { const cipherView: CipherView = this.buildNewVaultItemCipherView({ login, @@ -2267,7 +2404,10 @@ export class OverlayBackground implements OverlayBackgroundInterface { collectionIds: cipherView.collectionIds, }); - await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); + await this.openAddEditVaultItemPopout(sender.tab, { + cipherId: cipherView.id, + cipherType: addNewCipherType, + }); await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher"); } catch (error) { this.logService.error("Error building cipher and opening add/edit vault item popout", error); diff --git a/apps/browser/src/autofill/background/tabs.background.ts b/apps/browser/src/autofill/background/tabs.background.ts index ae57bd51cea..b07e06234d3 100644 --- a/apps/browser/src/autofill/background/tabs.background.ts +++ b/apps/browser/src/autofill/background/tabs.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import MainBackground from "../../background/main.background"; diff --git a/apps/browser/src/autofill/background/web-request.background.ts b/apps/browser/src/autofill/background/web-request.background.ts index 2eb976529f4..2c14358a359 100644 --- a/apps/browser/src/autofill/background/web-request.background.ts +++ b/apps/browser/src/autofill/background/web-request.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index c1567b46cd9..597d75575b0 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts index 21eadfaf668..79998b65205 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts @@ -1,12 +1,14 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -19,6 +21,7 @@ describe("context-menu", () => { let i18nService: MockProxy; let logService: MockProxy; let billingAccountProfileStateService: MockProxy; + let accountService: MockProxy; let removeAllSpy: jest.SpyInstance void]>; let createSpy: jest.SpyInstance< @@ -34,6 +37,7 @@ describe("context-menu", () => { i18nService = mock(); logService = mock(); billingAccountProfileStateService = mock(); + accountService = mock(); removeAllSpy = jest .spyOn(chrome.contextMenus, "removeAll") @@ -53,8 +57,15 @@ describe("context-menu", () => { i18nService, logService, billingAccountProfileStateService, + accountService, ); autofillSettingsService.enableContextMenu$ = of(true); + accountService.activeAccount$ = of({ + id: "userId" as UserId, + email: "", + emailVerified: false, + name: undefined, + }); }); afterEach(() => jest.resetAllMocks()); @@ -69,7 +80,7 @@ describe("context-menu", () => { }); it("has menu enabled, but does not have premium", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false)); const createdMenu = await sut.init(); expect(createdMenu).toBeTruthy(); @@ -77,7 +88,7 @@ describe("context-menu", () => { }); it("has menu enabled and has premium", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); const createdMenu = await sut.init(); expect(createdMenu).toBeTruthy(); @@ -131,16 +142,15 @@ describe("context-menu", () => { }); it("create entry for each cipher piece", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); await sut.loadOptions("TEST_TITLE", "1", createCipher()); - // One for autofill, copy username, copy password, and copy totp code expect(createSpy).toHaveBeenCalledTimes(4); }); it("creates a login/unlock item for each context menu action option when user is not authenticated", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); await sut.loadOptions("TEST_TITLE", "NOOP"); diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.ts index 7b074a566a2..41d88439e8f 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AUTOFILL_CARD_ID, AUTOFILL_ID, @@ -147,6 +150,7 @@ export class MainContextMenuHandler { private i18nService: I18nService, private logService: LogService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} /** @@ -166,11 +170,13 @@ export class MainContextMenuHandler { this.initRunning = true; try { + const account = await firstValueFrom(this.accountService.activeAccount$); + const hasPremium = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ); + for (const options of this.initContextMenuItems) { - if ( - options.checkPremiumAccess && - !(await firstValueFrom(this.billingAccountProfileStateService.hasPremiumFromAnySource$)) - ) { + if (options.checkPremiumAccess && !hasPremium) { continue; } @@ -265,8 +271,9 @@ export class MainContextMenuHandler { await createChildItem(COPY_USERNAME_ID); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ); if (canAccessPremium && (!cipher || !Utils.isNullOrEmpty(cipher.login?.totp))) { await createChildItem(COPY_VERIFICATION_CODE_ID); diff --git a/apps/browser/src/autofill/clipboard/clear-clipboard.ts b/apps/browser/src/autofill/clipboard/clear-clipboard.ts index 426d6539513..93674df0ad9 100644 --- a/apps/browser/src/autofill/clipboard/clear-clipboard.ts +++ b/apps/browser/src/autofill/clipboard/clear-clipboard.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BrowserApi } from "../../platform/browser/browser-api"; export class ClearClipboard { diff --git a/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts b/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts index cf3bc311aea..5fb6e8667a4 100644 --- a/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts +++ b/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, Subscription } from "rxjs"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; diff --git a/apps/browser/src/autofill/content/auto-submit-login.spec.ts b/apps/browser/src/autofill/content/auto-submit-login.spec.ts index ff1dbd4e945..d70fc1e7446 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.spec.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.spec.ts @@ -46,6 +46,8 @@ describe("AutoSubmitLogin content script", () => { beforeEach(() => { jest.useFakeTimers(); setupEnvironmentDefaults(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./auto-submit-login"); }); diff --git a/apps/browser/src/autofill/content/auto-submit-login.ts b/apps/browser/src/autofill/content/auto-submit-login.ts index e304247a66a..ca5c8ebee80 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import AutofillPageDetails from "../models/autofill-page-details"; diff --git a/apps/browser/src/autofill/content/autofill-init.ts b/apps/browser/src/autofill/content/autofill-init.ts index 42933c57b1e..8f69937ac60 100644 --- a/apps/browser/src/autofill/content/autofill-init.ts +++ b/apps/browser/src/autofill/content/autofill-init.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import AutofillPageDetails from "../models/autofill-page-details"; diff --git a/apps/browser/src/autofill/content/autofiller.ts b/apps/browser/src/autofill/content/autofiller.ts index 0ca9d37187a..c7a742f1fe1 100644 --- a/apps/browser/src/autofill/content/autofiller.ts +++ b/apps/browser/src/autofill/content/autofiller.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { setupExtensionDisconnectAction } from "../utils"; if (document.readyState === "loading") { diff --git a/apps/browser/src/autofill/content/bootstrap-autofill-overlay-menu.ts b/apps/browser/src/autofill/content/bootstrap-autofill-overlay-menu.ts index cd22e1e5353..605ffff0fec 100644 --- a/apps/browser/src/autofill/content/bootstrap-autofill-overlay-menu.ts +++ b/apps/browser/src/autofill/content/bootstrap-autofill-overlay-menu.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service"; import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service"; import DomElementVisibilityService from "../services/dom-element-visibility.service"; diff --git a/apps/browser/src/autofill/content/bootstrap-autofill-overlay-notifications.ts b/apps/browser/src/autofill/content/bootstrap-autofill-overlay-notifications.ts index 6fbb076389e..495ae0e22db 100644 --- a/apps/browser/src/autofill/content/bootstrap-autofill-overlay-notifications.ts +++ b/apps/browser/src/autofill/content/bootstrap-autofill-overlay-notifications.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OverlayNotificationsContentService } from "../overlay/notifications/content/overlay-notifications-content.service"; import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service"; import DomElementVisibilityService from "../services/dom-element-visibility.service"; diff --git a/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts b/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts index 11c8e4afd66..1777b135fe9 100644 --- a/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts +++ b/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service"; import { OverlayNotificationsContentService } from "../overlay/notifications/content/overlay-notifications-content.service"; import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service"; diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts new file mode 100644 index 00000000000..a9b4742b448 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -0,0 +1,66 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { border, themes, typography, spacing } from "../constants/styles"; + +export function ActionButton({ + buttonAction, + buttonText, + disabled = false, + theme, +}: { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}) { + const handleButtonClick = (event: Event) => { + if (!disabled) { + buttonAction(event); + } + }; + + return html` + + `; +} + +const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` + ${typography.body2} + + user-select: none; + border: 1px solid transparent; + border-radius: ${border.radius.full}; + padding: ${spacing["1"]} ${spacing["3"]}; + width: 100%; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + font-weight: 700; + + ${disabled + ? ` + background-color: ${themes[theme].secondary["300"]}; + color: ${themes[theme].text.muted}; + ` + : ` + background-color: ${themes[theme].primary["600"]}; + cursor: pointer; + color: ${themes[theme].text.contrast}; + + :hover { + border-color: ${themes[theme].primary["700"]}; + background-color: ${themes[theme].primary["700"]}; + color: ${themes[theme].text.contrast}; + } + `} +`; diff --git a/apps/browser/src/autofill/content/components/buttons/badge-button.ts b/apps/browser/src/autofill/content/components/buttons/badge-button.ts new file mode 100644 index 00000000000..3b3b84f8166 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/badge-button.ts @@ -0,0 +1,67 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { border, themes, typography, spacing } from "../constants/styles"; + +export function BadgeButton({ + buttonAction, + buttonText, + disabled = false, + theme, +}: { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}) { + const handleButtonClick = (event: Event) => { + if (!disabled) { + buttonAction(event); + } + }; + + return html` + + `; +} + +const badgeButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` + ${typography.helperMedium} + + user-select: none; + border-radius: ${border.radius.full}; + padding: ${spacing["1"]} ${spacing["2"]}; + max-height: fit-content; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + font-weight: 500; + + ${disabled + ? ` + border: 0.5px solid ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].secondary["300"]}; + color: ${themes[theme].text.muted}; + ` + : ` + border: 0.5px solid ${themes[theme].primary["700"]}; + background-color: ${themes[theme].primary["100"]}; + cursor: pointer; + color: ${themes[theme].primary["700"]}; + + :hover { + border-color: ${themes[theme].primary["600"]}; + background-color: ${themes[theme].primary["600"]}; + color: ${themes[theme].text.contrast}; + } + `} +`; diff --git a/apps/browser/src/autofill/content/components/buttons/close-button.ts b/apps/browser/src/autofill/content/components/buttons/close-button.ts new file mode 100644 index 00000000000..c32d0c130e3 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/close-button.ts @@ -0,0 +1,39 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { spacing, themes } from "../constants/styles"; +import { Close as CloseIcon } from "../icons"; + +export function CloseButton({ + handleCloseNotification, + theme, +}: { + handleCloseNotification: (e: Event) => void; + theme: Theme; +}) { + return html` + + `; +} + +const closeButtonStyles = (theme: Theme) => css` + border: 1px solid transparent; + border-radius: ${spacing["1"]}; + background-color: transparent; + cursor: pointer; + width: 36px; + height: 36px; + + :hover { + border: 1px solid ${themes[theme].primary["600"]}; + } + + > svg { + width: 20px; + height: 20px; + } +`; diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts new file mode 100644 index 00000000000..cacd2b59f0e --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -0,0 +1,62 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +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, +}: { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}) { + return html` + + `; +} + +const editButtonStyles = ({ disabled, theme }: { disabled?: boolean; theme: Theme }) => css` + ${typography.helperMedium} + + user-select: none; + display: flex; + border: 1px solid transparent; + border-radius: ${spacing["1"]}; + background-color: transparent; + padding: ${spacing["1"]}; + max-height: fit-content; + overflow: hidden; + + ${!disabled + ? ` + cursor: pointer; + + :hover { + border-color: ${themes[theme].primary["600"]}; + } + ` + : ""} + + > svg { + width: 16px; + height: fit-content; + } +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts new file mode 100644 index 00000000000..2d386d34d6a --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -0,0 +1,31 @@ +import { Theme } from "@bitwarden/common/platform/enums"; + +import { BadgeButton } from "../../../content/components/buttons/badge-button"; +import { EditButton } from "../../../content/components/buttons/edit-button"; +import { NotificationTypes } from "../../../notification/abstractions/notification-bar"; + +export function CipherAction({ + handleAction = () => { + /* no-op */ + }, + notificationType, + theme, +}: { + handleAction?: (e: Event) => void; + notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; + theme: Theme; +}) { + return notificationType === NotificationTypes.Change + ? BadgeButton({ + buttonAction: handleAction, + // @TODO localize + buttonText: "Update item", + theme, + }) + : EditButton({ + buttonAction: handleAction, + // @TODO localize + buttonText: "Edit item", + theme, + }); +} diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-icon.ts b/apps/browser/src/autofill/content/components/cipher/cipher-icon.ts new file mode 100644 index 00000000000..73d3f7604a9 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-icon.ts @@ -0,0 +1,33 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { Globe } from "../../../content/components/icons"; + +/** + * @param {string} props.color contextual color override if no icon URI is available + * @param {string} props.size valid CSS `width` value, represents the width-basis of the graphic, with height maintaining original aspect-ratio + */ +export function CipherIcon({ + color, + size, + theme, + uri, +}: { + color: string; + size: string; + theme: Theme; + uri?: string; +}) { + const iconClass = cipherIconStyle({ width: size }); + + return uri + ? html`` + : html`${Globe({ color, theme })}`; +} + +const cipherIconStyle = ({ width }: { width: string }) => css` + width: ${width}; + height: fit-content; +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts b/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts new file mode 100644 index 00000000000..38b4292f8e5 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts @@ -0,0 +1,35 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes } from "../../../content/components/constants/styles"; +import { Business, Family } from "../../../content/components/icons"; + +// @TODO connect data source to icon checks +// @TODO support other indicator types (attachments, etc) +export function CipherInfoIndicatorIcons({ + isBusinessOrg, + isFamilyOrg, + theme, +}: { + isBusinessOrg?: boolean; + isFamilyOrg?: boolean; + theme: Theme; +}) { + const indicatorIcons = [ + ...(isBusinessOrg ? [Business({ color: themes[theme].text.muted, theme })] : []), + ...(isFamilyOrg ? [Family({ color: themes[theme].text.muted, theme })] : []), + ]; + + return indicatorIcons.length + ? html` ${indicatorIcons} ` + : null; // @TODO null case should be handled by parent +} + +const cipherInfoIndicatorIconsStyles = css` + > svg { + width: fit-content; + height: 12px; + } +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-info.ts b/apps/browser/src/autofill/content/components/cipher/cipher-info.ts new file mode 100644 index 00000000000..de374b44a97 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-info.ts @@ -0,0 +1,48 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes, typography } from "../../../content/components/constants/styles"; + +import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons"; +import { CipherData } from "./types"; + +// @TODO support other cipher types (card, identity, notes, etc) +export function CipherInfo({ cipher, theme }: { cipher: CipherData; theme: Theme }) { + const { name, login } = cipher; + + return html` +
+ + ${[name, CipherInfoIndicatorIcons({ theme })]} + + + ${login?.username + ? html`${login.username}` + : null} +
+ `; +} + +const cipherInfoPrimaryTextStyles = (theme: Theme) => css` + ${typography.body2} + + gap: 2px; + display: flex; + display: block; + overflow-x: hidden; + text-overflow: ellipsis; + color: ${themes[theme].text.main}; + font-weight: 500; +`; + +const cipherInfoSecondaryTextStyles = (theme: Theme) => css` + ${typography.helperMedium} + + display: block; + overflow-x: hidden; + text-overflow: ellipsis; + color: ${themes[theme].text.muted}; + font-weight: 400; +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts new file mode 100644 index 00000000000..651c20cac3a --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -0,0 +1,65 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { spacing, themes } from "../../../content/components/constants/styles"; +import { + NotificationType, + NotificationTypes, +} from "../../../notification/abstractions/notification-bar"; + +import { CipherAction } from "./cipher-action"; +import { CipherIcon } from "./cipher-icon"; +import { CipherInfo } from "./cipher-info"; +import { CipherData } from "./types"; + +const cipherIconWidth = "24px"; + +export function CipherItem({ + cipher, + handleAction, + notificationType, + theme = ThemeTypes.Light, +}: { + cipher: CipherData; + handleAction?: (e: Event) => void; + notificationType?: NotificationType; + theme: Theme; +}) { + const { icon } = cipher; + const uri = (icon.imageEnabled && icon.image) || undefined; + + let cipherActionButton = null; + + if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { + cipherActionButton = html`
+ ${CipherAction({ handleAction, notificationType, theme })} +
`; + } + + return html` +
+ ${CipherIcon({ color: themes[theme].text.muted, size: cipherIconWidth, theme, uri })} + ${CipherInfo({ theme, cipher })} +
+ ${cipherActionButton} + `; +} + +const cipherItemStyles = css` + gap: ${spacing["2"]}; + display: flex; + flex-wrap: nowrap; + align-items: center; + justify-content: start; + + > img, + > span { + display: flex; + } + + > div { + max-width: calc(100% - ${cipherIconWidth} - ${spacing["2"]}); + } +`; diff --git a/apps/browser/src/autofill/content/components/cipher/index.ts b/apps/browser/src/autofill/content/components/cipher/index.ts new file mode 100644 index 00000000000..733ddb74b4d --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/index.ts @@ -0,0 +1,5 @@ +export * from "./cipher-action"; +export * from "./cipher-icon"; +export * from "./cipher-indicator-icons"; +export * from "./cipher-info"; +export * from "./cipher-item"; diff --git a/apps/browser/src/autofill/content/components/cipher/types.ts b/apps/browser/src/autofill/content/components/cipher/types.ts new file mode 100644 index 00000000000..acdee756570 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/types.ts @@ -0,0 +1,48 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const CipherTypes = { + Login: 1, + SecureNote: 2, + Card: 3, + Identity: 4, +} as const; + +type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes]; + +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const CipherRepromptTypes = { + None: 0, + Password: 1, +} as const; + +type CipherRepromptType = (typeof CipherRepromptTypes)[keyof typeof CipherRepromptTypes]; + +export type WebsiteIconData = { + imageEnabled: boolean; + image: string; + fallbackImage: string; + icon: string; +}; + +export type CipherData = { + id: string; + name: string; + type: CipherType; + reprompt: CipherRepromptType; + favorite: boolean; + icon: WebsiteIconData; + accountCreationFieldType?: string; + login?: { + username: string; + passkey: { + rpName: string; + userName: string; + } | null; + }; + card?: string; + identity?: { + fullName: string; + username?: string; + }; +}; diff --git a/apps/browser/src/autofill/content/components/constants/styles.ts b/apps/browser/src/autofill/content/components/constants/styles.ts new file mode 100644 index 00000000000..cd6054e90ba --- /dev/null +++ b/apps/browser/src/autofill/content/components/constants/styles.ts @@ -0,0 +1,206 @@ +import { Theme } from "@bitwarden/common/platform/enums"; + +const lightTheme = { + transparent: { + hover: `rgb(0 0 0 / 0.02)`, + }, + shadow: `rgba(168 179 200)`, + primary: { + 100: `rgba(219, 229, 246)`, + 300: `rgba(121, 161, 233)`, + 600: `rgba(23, 93, 220)`, + 700: `rgba(26, 65, 172)`, + }, + secondary: { + 100: `rgba(230, 233, 239)`, + 300: `rgba(168, 179, 200)`, + 500: `rgba(90, 109, 145)`, + 600: `rgba(83, 99, 131)`, + 700: `rgba(63, 75, 99)`, + }, + success: { + 100: `rgba(219, 229, 246)`, + 600: `rgba(121, 161, 233)`, + 700: `rgba(26, 65, 172)`, + }, + danger: { + 100: `rgba(255, 236, 239)`, + 600: `rgba(203, 38, 58)`, + 700: `rgba(149, 27, 42)`, + }, + warning: { + 100: `rgba(255, 248, 228)`, + 600: `rgba(255, 191, 0)`, + 700: `rgba(172, 88, 0)`, + }, + info: { + 100: `rgba(219, 229, 246)`, + 600: `rgba(121, 161, 233)`, + 700: `rgba(26, 65, 172)`, + }, + art: { + primary: `rgba(2, 15, 102)`, + accent: `rgba(44, 221, 223)`, + }, + text: { + main: `rgba(27, 32, 41)`, + muted: `rgba(90, 109, 145)`, + contrast: `rgba(255, 255, 255)`, + alt2: `rgba(255, 255, 255)`, + code: `rgba(192, 17, 118)`, + }, + background: { + DEFAULT: `rgba(255, 255, 255)`, + alt: `rgba(243, 246, 249)`, + alt2: `rgba(23, 92, 219)`, + alt3: `rgba(26, 65, 172)`, + alt4: `rgba(2, 15, 102)`, + }, + brandLogo: `rgba(23, 93, 220)`, +}; + +const darkTheme = { + transparent: { + hover: `rgb(255 255 255 / 0.02)`, + }, + shadow: `rgba(0, 0, 0)`, + primary: { + 100: `rgba(26, 39, 78)`, + 300: `rgba(26, 65, 172)`, + 600: `rgba(101, 171, 255)`, + 700: `rgba(170, 195, 239)`, + }, + secondary: { + 100: `rgba(48, 57, 70)`, + 300: `rgba(82, 91, 106)`, + 500: `rgba(121, 128, 142)`, + 600: `rgba(143, 152, 166)`, + 700: `rgba(158, 167, 181)`, + }, + success: { + 100: `rgba(11, 111, 21)`, + 600: `rgba(107, 241, 120)`, + 700: `rgba(191, 236, 195)`, + }, + danger: { + 100: `rgba(149, 27, 42)`, + 600: `rgba(255, 78, 99)`, + 700: `rgba(255, 236, 239)`, + }, + warning: { + 100: `rgba(172, 88, 0)`, + 600: `rgba(255, 191, 0)`, + 700: `rgba(255, 248, 228)`, + }, + info: { + 100: `rgba(26, 65, 172)`, + 600: `rgba(121, 161, 233)`, + 700: `rgba(219, 229, 246)`, + }, + art: { + primary: `rgba(243, 246, 249)`, + accent: `rgba(44, 221, 233)`, + }, + text: { + main: `rgba(243, 246, 249)`, + muted: `rgba(136, 152, 181)`, + contrast: `rgba(18 26 39)`, + alt2: `rgba(255, 255, 255)`, + code: `rgba(255, 143, 208)`, + }, + background: { + DEFAULT: `rgba(32, 39, 51)`, + alt: `rgba(18, 26, 39)`, + alt2: `rgba(47, 52, 61)`, + alt3: `rgba(48, 57, 70)`, + alt4: `rgba(18, 26, 39)`, + }, + brandLogo: `rgba(255, 255, 255)`, +}; + +export const themes = { + light: lightTheme, + dark: darkTheme, + + // For compatibility + system: lightTheme, + nord: lightTheme, + solarizedDark: darkTheme, +}; + +export const spacing = { + 4: `16px`, + 3: `12px`, + 2: `8px`, + "1.5": `6px`, + 1: `4px`, +}; + +export const border = { + radius: { + large: `8px`, + full: `9999px`, + }, +}; + +export const typography = { + body1: ` + line-height: 24px; + font-family: "DM Sans", sans-serif; + font-size: 16px; + `, + body2: ` + line-height: 20px; + font-family: "DM Sans", sans-serif; + font-size: 14px; + `, + helperMedium: ` + line-height: 16px; + font-family: "DM Sans", sans-serif; + font-size: 12px; + `, +}; + +export const ruleNames = { + fill: "fill", + stroke: "stroke", +} as const; + +type RuleName = (typeof ruleNames)[keyof typeof ruleNames]; + +/* + * `color` is an intentionally generic name here, since either fill or stroke may apply, due to + * inconsistent SVG construction. This consequently precludes dynamic multi-colored icons here. + */ +export const buildIconColorRule = (color: string, rule: RuleName = ruleNames.fill) => ` + ${rule}: ${color}; +`; + +export function scrollbarStyles(theme: Theme) { + return { + default: ` + /* FireFox & Chrome support */ + scrollbar-color: ${themes[theme].secondary["500"]} ${themes[theme].background.alt}; + `, + safari: ` + /* Safari Support */ + ::-webkit-scrollbar { + overflow: auto; + } + ::-webkit-scrollbar-thumb { + border-width: 4px; + border-style: solid; + border-radius: 0.5rem; + border-color: transparent; + background-clip: content-box; + background-color: ${themes[theme].secondary["500"]}; + } + ::-webkit-scrollbar-track { + ${themes[theme].background.alt}; + } + ::-webkit-scrollbar-thumb:hover { + ${themes[theme].secondary["600"]}; + } + `, + }; +} diff --git a/apps/browser/src/autofill/content/components/dropdown-menu.ts b/apps/browser/src/autofill/content/components/dropdown-menu.ts new file mode 100644 index 00000000000..3e3874b37d7 --- /dev/null +++ b/apps/browser/src/autofill/content/components/dropdown-menu.ts @@ -0,0 +1,121 @@ +import { css } from "@emotion/css"; +import { html, TemplateResult } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { border, themes, typography, spacing } from "./constants/styles"; +import { AngleDown } from "./icons"; + +export function DropdownMenu({ + buttonText, + icon, + disabled = false, + selectAction, + theme, +}: { + selectAction?: (e: Event) => void; + buttonText: string; + icon?: TemplateResult; + disabled?: boolean; + theme: Theme; +}) { + // @TODO placeholder/will not work; make stateful + const showDropdown = false; + const handleButtonClick = (event: Event) => { + // if (!disabled) { + // // show dropdown + // showDropdown = !showDropdown; + // this.requestUpdate(); + // } + }; + + const dropdownMenuItems: TemplateResult[] = []; + + return html` +
+ + ${showDropdown + ? html`
${dropdownMenuItems}
` + : null} +
+ `; +} + +const iconSize = "15px"; + +const dropdownContainerStyles = css` + display: flex; + + > div, + > button { + width: 100%; + } +`; + +const dropdownButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` + ${typography.body2} + + font-weight: 400; + gap: ${spacing["1.5"]}; + user-select: none; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + border-radius: ${border.radius.full}; + padding: ${spacing["1"]} ${spacing["2"]}; + max-height: fit-content; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + + > svg { + max-width: ${iconSize}; + height: fit-content; + } + + ${disabled + ? ` + border: 1px solid ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].secondary["300"]}; + color: ${themes[theme].text.muted}; + ` + : ` + border: 1px solid ${themes[theme].text.muted}; + background-color: transparent; + cursor: pointer; + color: ${themes[theme].text.muted}; + + :hover { + border-color: ${themes[theme].secondary["700"]}; + background-color: ${themes[theme].secondary["100"]}; + } + `} +`; + +const dropdownButtonTextStyles = css` + max-width: calc(100% - ${iconSize} - ${iconSize}); + overflow-x: hidden; + text-overflow: ellipsis; +`; + +const dropdownMenuStyles = ({ theme }: { theme: Theme }) => css` + color: ${themes[theme].text.main}; + border: 1px solid ${themes[theme].secondary["500"]}; + border-radius: 0.5rem; + background-clip: padding-box; + background-color: ${themes[theme].background.DEFAULT}; + padding: 0.25rem 0.75rem; + position: absolute; + overflow-y: auto; +`; diff --git a/apps/browser/src/autofill/content/components/icons/angle-down.ts b/apps/browser/src/autofill/content/components/icons/angle-down.ts new file mode 100644 index 00000000000..4b85319c18a --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/angle-down.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function AngleDown({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/brand-icon-container.ts b/apps/browser/src/autofill/content/components/icons/brand-icon-container.ts new file mode 100644 index 00000000000..8df68d79b6e --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/brand-icon-container.ts @@ -0,0 +1,19 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { Shield } from "./shield"; + +export function BrandIconContainer({ iconLink, theme }: { iconLink?: URL; theme: Theme }) { + const Icon = html`
${Shield({ theme })}
`; + + return iconLink ? html`${Icon}` : Icon; +} + +const brandIconContainerStyles = css` + > svg { + width: 20px; + height: fit-content; + } +`; diff --git a/apps/browser/src/autofill/content/components/icons/business.ts b/apps/browser/src/autofill/content/components/icons/business.ts new file mode 100644 index 00000000000..547ee82b547 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/business.ts @@ -0,0 +1,46 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Business({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/close.ts b/apps/browser/src/autofill/content/components/icons/close.ts new file mode 100644 index 00000000000..c94a4b20a6a --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/close.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Close({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts b/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts new file mode 100644 index 00000000000..bcc7b3d5432 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function ExclamationTriangle({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/family.ts b/apps/browser/src/autofill/content/components/icons/family.ts new file mode 100644 index 00000000000..33e2e422ced --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/family.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Family({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/folder.ts b/apps/browser/src/autofill/content/components/icons/folder.ts new file mode 100644 index 00000000000..7e1f8f197f6 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/folder.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Folder({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/globe.ts b/apps/browser/src/autofill/content/components/icons/globe.ts new file mode 100644 index 00000000000..6697fa93b70 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/globe.ts @@ -0,0 +1,28 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Globe({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/index.ts b/apps/browser/src/autofill/content/components/icons/index.ts new file mode 100644 index 00000000000..992b034afa7 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/index.ts @@ -0,0 +1,12 @@ +export { AngleDown } from "./angle-down"; +export { BrandIconContainer } from "./brand-icon-container"; +export { Business } from "./business"; +export { Close } from "./close"; +export { ExclamationTriangle } from "./exclamation-triangle"; +export { Family } from "./family"; +export { Folder } from "./folder"; +export { Globe } from "./globe"; +export { PartyHorn } from "./party-horn"; +export { PencilSquare } from "./pencil-square"; +export { Shield } from "./shield"; +export { User } from "./user"; diff --git a/apps/browser/src/autofill/content/components/icons/party-horn.ts b/apps/browser/src/autofill/content/components/icons/party-horn.ts new file mode 100644 index 00000000000..dc2144b524f --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/party-horn.ts @@ -0,0 +1,174 @@ +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +// This icon has static multi-colors for each theme +export function PartyHorn({ theme }: { theme: Theme }) { + if (theme === ThemeTypes.Dark) { + return html` + + + + + + + + + + + + + + `; + } + + return html` + + + + + + + + + + + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/pencil-square.ts b/apps/browser/src/autofill/content/components/icons/pencil-square.ts new file mode 100644 index 00000000000..45a8429f883 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/pencil-square.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function PencilSquare({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/shield.ts b/apps/browser/src/autofill/content/components/icons/shield.ts new file mode 100644 index 00000000000..5ffd953e869 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/shield.ts @@ -0,0 +1,19 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Shield({ color, theme }: { color?: string; theme: Theme }) { + const shapeColor = color || themes[theme].brandLogo; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/user.ts b/apps/browser/src/autofill/content/components/icons/user.ts new file mode 100644 index 00000000000..6babcfa39a9 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/user.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function User({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts new file mode 100644 index 00000000000..6a3ed2e5d1e --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -0,0 +1,69 @@ +import createEmotion from "@emotion/css/create-instance"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { NotificationType } from "../../../notification/abstractions/notification-bar"; +import { CipherItem } from "../cipher"; +import { CipherData } from "../cipher/types"; +import { scrollbarStyles, spacing, themes, typography } from "../constants/styles"; +import { ItemRow } from "../rows/item-row"; + +export const componentClassPrefix = "notification-body"; + +const { css } = createEmotion({ + key: componentClassPrefix, +}); + +export function NotificationBody({ + ciphers, + notificationType, + theme = ThemeTypes.Light, +}: { + ciphers: CipherData[]; + customClasses?: string[]; + notificationType?: NotificationType; + theme: Theme; +}) { + // @TODO get client vendor from context + const isSafari = false; + + return html` +
+ ${ciphers.map((cipher) => + ItemRow({ + theme, + children: CipherItem({ + cipher, + notificationType, + theme, + handleAction: () => { + // @TODO connect update or edit actions to handler + }, + }), + }), + )} +
+ `; +} + +const notificationBodyStyles = ({ isSafari, theme }: { isSafari: boolean; theme: Theme }) => css` + ${typography.body1} + + gap: ${spacing["1.5"]}; + display: flex; + flex-flow: column; + background-color: ${themes[theme].background.alt}; + max-height: 123px; + overflow-x: hidden; + overflow-y: auto; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-weight: 400; + + :last-child { + border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; + } + + ${isSafari ? scrollbarStyles(theme).safari : scrollbarStyles(theme).default} +`; diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts new file mode 100644 index 00000000000..0cce066cf3a --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -0,0 +1,99 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { + NotificationBarIframeInitData, + NotificationTypes, + NotificationType, +} from "../../../notification/abstractions/notification-bar"; +import { createAutofillOverlayCipherDataMock } from "../../../spec/autofill-mocks"; +import { CipherData } from "../cipher/types"; +import { themes, spacing } from "../constants/styles"; + +import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body"; +import { NotificationFooter } from "./footer"; +import { + NotificationHeader, + componentClassPrefix as notificationHeaderClassPrefix, +} from "./header"; + +export function NotificationContainer({ + handleCloseNotification, + i18n, + theme = ThemeTypes.Light, + type, +}: NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void } & { + i18n: { [key: string]: string }; + type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` +}) { + const headerMessage = getHeaderMessage(i18n, type); + const showBody = true; + + // @TODO remove mock ciphers for development + const ciphers = [ + createAutofillOverlayCipherDataMock(1), + { ...createAutofillOverlayCipherDataMock(2), icon: { imageEnabled: false } }, + { + ...createAutofillOverlayCipherDataMock(3), + icon: { imageEnabled: true, image: "https://localhost:8443/icons/webtests.dev/icon.png" }, + }, + ] as CipherData[]; + + return html` +
+ ${NotificationHeader({ + handleCloseNotification, + standalone: showBody, + message: headerMessage, + theme, + })} + ${showBody + ? NotificationBody({ + ciphers, + notificationType: type, + theme, + }) + : null} + ${NotificationFooter({ + theme, + notificationType: type, + })} +
+ `; +} + +const notificationContainerStyles = (theme: Theme) => css` + position: absolute; + right: 20px; + border: 1px solid ${themes[theme].secondary["300"]}; + border-radius: ${spacing["4"]}; + box-shadow: -2px 4px 6px 0px #0000001a; + background-color: ${themes[theme].background.alt}; + width: 400px; + + [class*="${notificationHeaderClassPrefix}-"] { + border-radius: ${spacing["4"]} ${spacing["4"]} 0 0; + } + + [class*="${notificationBodyClassPrefix}-"] { + margin: ${spacing["3"]} 0 ${spacing["1.5"]} ${spacing["3"]}; + padding-right: ${spacing["3"]}; + } +`; + +function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationType) { + switch (type) { + case NotificationTypes.Add: + return i18n.saveAsNewLoginAction; + case NotificationTypes.Change: + return i18n.updateLoginPrompt; + case NotificationTypes.Unlock: + return ""; + case NotificationTypes.FilelessImport: + return ""; + default: + return undefined; + } +} diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts new file mode 100644 index 00000000000..91a72dc9aab --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -0,0 +1,42 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { + NotificationType, + NotificationTypes, +} from "../../../notification/abstractions/notification-bar"; +import { spacing, themes } from "../constants/styles"; +import { ActionRow } from "../rows/action-row"; +import { ButtonRow } from "../rows/button-row"; + +export function NotificationFooter({ + notificationType, + theme, +}: { + notificationType?: NotificationType; + theme: Theme; +}) { + const isChangeNotification = notificationType === NotificationTypes.Change; + // @TODO localize + const saveNewItemText = "Save as new login"; + + return html` +
+ ${isChangeNotification + ? ActionRow({ itemText: saveNewItemText, handleAction: () => {}, theme }) + : ButtonRow({ theme })} +
+ `; +} + +const notificationFooterStyles = ({ theme }: { theme: Theme }) => css` + display: flex; + background-color: ${themes[theme].background.alt}; + padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]}; + + :last-child { + border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; + } +`; diff --git a/apps/browser/src/autofill/content/components/notification/header-message.ts b/apps/browser/src/autofill/content/components/notification/header-message.ts new file mode 100644 index 00000000000..ccfa58b8970 --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/header-message.ts @@ -0,0 +1,25 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes } from "../constants/styles"; + +export function NotificationHeaderMessage({ message, theme }: { message: string; theme: Theme }) { + return html` + ${message} + `; +} + +const notificationHeaderMessageStyles = (theme: Theme) => css` + flex-grow: 1; + overflow-x: hidden; + text-align: left; + text-overflow: ellipsis; + line-height: 28px; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-family: "DM Sans", sans-serif; + font-size: 18px; + font-weight: 600; +`; diff --git a/apps/browser/src/autofill/content/components/notification/header.ts b/apps/browser/src/autofill/content/components/notification/header.ts new file mode 100644 index 00000000000..85f6e48cd5d --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/header.ts @@ -0,0 +1,61 @@ +import createEmotion from "@emotion/css/create-instance"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { CloseButton } from "../buttons/close-button"; +import { themes } from "../constants/styles"; +import { BrandIconContainer } from "../icons/brand-icon-container"; + +import { NotificationHeaderMessage } from "./header-message"; + +export const componentClassPrefix = "notification-header"; + +const { css } = createEmotion({ + key: componentClassPrefix, +}); + +export function NotificationHeader({ + message, + standalone, + theme = ThemeTypes.Light, + handleCloseNotification, +}: { + message?: string; + standalone: boolean; + theme: Theme; + handleCloseNotification: (e: Event) => void; +}) { + const showIcon = true; + const isDismissable = true; + + return html` +
+ ${showIcon ? BrandIconContainer({ theme }) : null} + ${message ? NotificationHeaderMessage({ message, theme }) : null} + ${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null} +
+ `; +} + +const notificationHeaderStyles = ({ + standalone, + theme, +}: { + standalone: boolean; + theme: Theme; +}) => css` + gap: 8px; + display: flex; + align-items: center; + justify-content: flex-start; + background-color: ${themes[theme].background.alt}; + padding: 12px 16px 8px 16px; + white-space: nowrap; + + ${standalone + ? css` + border-bottom: 0.5px solid ${themes[theme].secondary["300"]}; + ` + : css``} +`; diff --git a/apps/browser/src/autofill/content/components/rows/action-row.ts b/apps/browser/src/autofill/content/components/rows/action-row.ts new file mode 100644 index 00000000000..ad58411baf4 --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/action-row.ts @@ -0,0 +1,53 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { spacing, themes, typography } from "../../../content/components/constants/styles"; + +export function ActionRow({ + handleAction, + itemText, + theme = ThemeTypes.Light, +}: { + itemText: string; + handleAction?: (e: Event) => void; + theme: Theme; +}) { + return html` + + `; +} + +const actionRowStyles = (theme: Theme) => css` + ${typography.body2} + + user-select: none; + border-width: 0 0 0.5px 0; + border-style: solid; + border-radius: ${spacing["2"]}; + border-color: ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].background.DEFAULT}; + cursor: pointer; + padding: ${spacing["2"]} ${spacing["3"]}; + width: 100%; + min-height: 40px; + text-align: left; + color: ${themes[theme].primary["600"]}; + font-weight: 700; + + > span { + display: block; + width: calc(100% - 5px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + :hover { + background-color: ${themes[theme].primary["100"]}; + color: ${themes[theme].primary["600"]}; + } +`; diff --git a/apps/browser/src/autofill/content/components/rows/button-row.ts b/apps/browser/src/autofill/content/components/rows/button-row.ts new file mode 100644 index 00000000000..ce14a242e97 --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/button-row.ts @@ -0,0 +1,73 @@ +import { css } from "@emotion/css"; +import { html, TemplateResult } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { ActionButton } from "../../../content/components/buttons/action-button"; +import { spacing, themes } from "../../../content/components/constants/styles"; +import { Folder, User } from "../../../content/components/icons"; +import { DropdownMenu } from "../dropdown-menu"; + +export function ButtonRow({ theme }: { theme: Theme }) { + return html` +
+ ${[ + ActionButton({ + buttonAction: () => {}, + buttonText: "Action Button", + theme, + }), + DropdownContainer({ + children: [ + DropdownMenu({ + buttonText: "You", + icon: User({ color: themes[theme].text.muted, theme }), + theme, + }), + DropdownMenu({ + buttonText: "Folder", + icon: Folder({ color: themes[theme].text.muted, theme }), + disabled: true, + theme, + }), + ], + }), + ]} +
+ `; +} + +function DropdownContainer({ children }: { children: TemplateResult[] }) { + return html`
${children}
`; +} + +const buttonRowStyles = css` + gap: 16px; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-height: 52px; + white-space: nowrap; + + > button { + max-width: min-content; + flex: 1 1 50%; + } + + > div { + flex: 1 1 min-content; + } +`; + +const dropdownContainerStyles = css` + gap: 8px; + display: flex; + align-items: center; + justify-content: flex-end; + overflow: hidden; + + > div { + min-width: calc(50% - ${spacing["1.5"]}); + } +`; diff --git a/apps/browser/src/autofill/content/components/rows/item-row.ts b/apps/browser/src/autofill/content/components/rows/item-row.ts new file mode 100644 index 00000000000..da00fd276ab --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/item-row.ts @@ -0,0 +1,56 @@ +import { css } from "@emotion/css"; +import { html, TemplateResult } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { spacing, themes, typography } from "../../../content/components/constants/styles"; + +export function ItemRow({ + theme = ThemeTypes.Light, + children, +}: { + theme: Theme; + children: TemplateResult | TemplateResult[]; +}) { + return html`
${children}
`; +} + +export const itemRowStyles = ({ theme }: { theme: Theme }) => css` + ${typography.body1} + + gap: ${spacing["2"]}; + display: flex; + align-items: center; + justify-content: space-between; + border-width: 0 0 0.5px 0; + border-style: solid; + border-radius: ${spacing["2"]}; + border-color: ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].background.DEFAULT}; + padding: ${spacing["2"]} ${spacing["3"]}; + min-height: min-content; + max-height: 52px; + overflow-x: hidden; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-weight: 400; + + > div { + :first-child { + flex: 3 3 75%; + min-width: 25%; + } + + :not(:first-child) { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end; + max-width: 25%; + + > button { + max-width: min-content; + } + } + } +`; diff --git a/apps/browser/src/autofill/content/content-message-handler.spec.ts b/apps/browser/src/autofill/content/content-message-handler.spec.ts index 226fcb4bd61..a37a2e07678 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -19,6 +19,8 @@ describe("ContentMessageHandler", () => { ); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./content-message-handler"); }); diff --git a/apps/browser/src/autofill/content/context-menu-handler.ts b/apps/browser/src/autofill/content/context-menu-handler.ts index 35c2124a1fd..82cf95afc81 100644 --- a/apps/browser/src/autofill/content/context-menu-handler.ts +++ b/apps/browser/src/autofill/content/context-menu-handler.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore const inputTags = ["input", "textarea", "select"]; const labelTags = ["label", "span"]; const attributes = ["id", "name", "label-aria", "placeholder"]; diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index 5217ebbe8ed..d3e9c29ab58 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -1,4 +1,7 @@ -import { ServerConfig } from "../../../../../libs/common/src/platform/abstractions/config/server-config"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; + import { AddLoginMessageData, ChangePasswordMessageData, diff --git a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts index 1ad985bc8e9..317f63e756c 100644 --- a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts +++ b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts @@ -6,6 +6,8 @@ describe("TriggerAutofillScriptInjection", () => { describe("init", () => { it("sends a message to the extension background", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../content/trigger-autofill-script-injection"); expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts index 3adaf9e276c..2c22097f3d0 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts @@ -12,6 +12,7 @@ import { DefaultDomainSettingsService, DomainSettingsService, } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService, Region, @@ -61,6 +62,7 @@ describe("OverlayBackground", () => { let overlayBackground: LegacyOverlayBackground; const cipherService = mock(); const autofillService = mock(); + let configService: MockProxy; let activeAccountStatusMock$: BehaviorSubject; let authService: MockProxy; @@ -92,7 +94,9 @@ describe("OverlayBackground", () => { }; beforeEach(() => { - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(true)); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts index 1a5d49e9e1f..5dfade0f863 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; diff --git a/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts b/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts index b3ee2637b09..fac9c0852f5 100644 --- a/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/content/autofill-init.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AutofillInit } from "../../content/abstractions/autofill-init"; import AutofillPageDetails from "../../models/autofill-page-details"; import { CollectAutofillContentService } from "../../services/collect-autofill-content.service"; diff --git a/apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe.service.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe.service.deprecated.ts index fa43c928175..402c384b8be 100644 --- a/apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe.service.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/iframe-content/autofill-overlay-iframe.service.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/button/autofill-overlay-button.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/button/autofill-overlay-button.deprecated.ts index b372bf292f1..a39ed99d424 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/button/autofill-overlay-button.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/button/autofill-overlay-button.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts index fde98a58a5f..fd6a79733cb 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayButton from "./autofill-overlay-button.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-button.scss"); (function () { diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/list/autofill-overlay-list.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/list/autofill-overlay-list.deprecated.ts index bb0642f84ab..2ab38fe5906 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/list/autofill-overlay-list.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/list/autofill-overlay-list.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts index 714ccbfbee5..5d587bd4293 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayList from "./autofill-overlay-list.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-list.scss"); (function () { diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/shared/autofill-overlay-page-element.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/shared/autofill-overlay-page-element.deprecated.ts index 85a59c1d3c5..c388c9c307c 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/shared/autofill-overlay-page-element.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/shared/autofill-overlay-page-element.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { RedirectFocusDirection } from "../../../../enums/autofill-overlay.enum"; diff --git a/apps/browser/src/autofill/deprecated/services/autofill-overlay-content.service.deprecated.ts b/apps/browser/src/autofill/deprecated/services/autofill-overlay-content.service.deprecated.ts index 27ec68bc678..cb281977f14 100644 --- a/apps/browser/src/autofill/deprecated/services/autofill-overlay-content.service.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/services/autofill-overlay-content.service.deprecated.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { FocusableElement, tabbable } from "tabbable"; diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 99ed4619954..144af0c0a35 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -25,6 +25,7 @@ import { BrowserScriptInjectorService } from "../../../platform/services/browser import { AbortManager } from "../../../vault/background/abort-manager"; import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; +import { BrowserFido2ParentWindowReference } from "../services/browser-fido2-user-interface.service"; import { Fido2ExtensionMessage } from "./abstractions/fido2.background"; import { Fido2Background } from "./fido2.background"; @@ -56,7 +57,7 @@ describe("Fido2Background", () => { let senderMock!: MockProxy; let logService!: MockProxy; let fido2ActiveRequestManager: MockProxy; - let fido2ClientService!: MockProxy; + let fido2ClientService!: MockProxy>; let vaultSettingsService!: MockProxy; let scriptInjectorServiceMock!: MockProxy; let configServiceMock!: MockProxy; @@ -73,7 +74,7 @@ describe("Fido2Background", () => { }); senderMock = mock({ id: "1", tab: tabMock }); logService = mock(); - fido2ClientService = mock(); + fido2ClientService = mock>(); vaultSettingsService = mock(); abortManagerMock = mock(); abortController = mock(); diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index 810cdf74657..e20a0584d20 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, startWith, Subscription } from "rxjs"; import { pairwise } from "rxjs/operators"; @@ -21,10 +23,11 @@ import { ScriptInjectorService } from "../../../platform/services/abstractions/s import { AbortManager } from "../../../vault/background/abort-manager"; import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; +import { BrowserFido2ParentWindowReference } from "../services/browser-fido2-user-interface.service"; import { - Fido2Background as Fido2BackgroundInterface, Fido2BackgroundExtensionMessageHandlers, + Fido2Background as Fido2BackgroundInterface, Fido2ExtensionMessage, SharedFido2ScriptInjectionDetails, SharedFido2ScriptRegistrationOptions, @@ -54,7 +57,7 @@ export class Fido2Background implements Fido2BackgroundInterface { constructor( private logService: LogService, private fido2ActiveRequestManager: Fido2ActiveRequestManager, - private fido2ClientService: Fido2ClientService, + private fido2ClientService: Fido2ClientService, private vaultSettingsService: VaultSettingsService, private scriptInjectorService: ScriptInjectorService, private configService: ConfigService, diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts index 94bef354a79..8885ed6299c 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts @@ -60,6 +60,8 @@ describe("Fido2 Content Script", () => { chrome.runtime.connect = jest.fn(() => portSpy); it("destroys the messenger when the port is disconnected", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); triggerPortOnDisconnectEvent(portSpy); @@ -75,6 +77,8 @@ describe("Fido2 Content Script", () => { const mockResult = { credentialId: "mock" } as CreateCredentialResult; jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue(mockResult); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const response = await messenger.handler!(message, new AbortController()); @@ -99,6 +103,8 @@ describe("Fido2 Content Script", () => { data: mock(), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, new AbortController()); @@ -121,6 +127,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); const abortSpy = jest.spyOn(abortController.signal, "removeEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -141,6 +149,8 @@ describe("Fido2 Content Script", () => { abortController.abort(); }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -161,6 +171,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue({ error: errorMessage }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const result = messenger.handler!(message, abortController); @@ -175,6 +187,8 @@ describe("Fido2 Content Script", () => { contentType: "application/json", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); @@ -193,6 +207,8 @@ describe("Fido2 Content Script", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts index 737f8e77ca3..f8352fc27a6 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AssertCredentialParams, CreateCredentialParams, diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index 6b9b41b5aac..69e17d26fe5 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -22,6 +22,8 @@ describe("FIDO2 page-script for manifest v2", () => { it("skips appending the `page-script.js` file if the document contentType is not `text/html`", () => { Object.defineProperty(window.document, "contentType", { value: "text/plain", writable: true }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).not.toHaveBeenCalled(); @@ -33,6 +35,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); @@ -48,6 +52,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index dfc2bba681a..4c1761c37ba 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { WebauthnUtils } from "../utils/webauthn-utils"; import { MessageType } from "./messaging/message"; @@ -265,6 +267,8 @@ import { Messenger } from "./messaging/messenger"; clearWaitForFocus(); void messenger.destroy(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { /** empty */ } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 31e8c941e86..f1aec69193b 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -55,6 +55,8 @@ describe("Fido2 page script with native WebAuthn support", () => { setupMockedWebAuthnSupport(); beforeAll(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); }); @@ -166,6 +168,8 @@ describe("Fido2 page script with native WebAuthn support", () => { contentType: "json/application", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); @@ -184,6 +188,8 @@ describe("Fido2 page script with native WebAuthn support", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index e354453ca59..af1838ec942 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -50,6 +50,8 @@ describe("Fido2 page script without native WebAuthn support", () => { const mockCreateCredentialsResult = createCreateCredentialResultMock(); const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); const mockCredentialAssertResult = createAssertCredentialResultMock(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); afterEach(() => { diff --git a/apps/browser/src/autofill/fido2/content/messaging/messenger.ts b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts index ea4049ac64c..a5530d87a8e 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/messenger.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { Message, MessageType } from "./message"; diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index 3ab9edf60f9..04b09a7df32 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BehaviorSubject, EmptyError, @@ -109,11 +111,15 @@ export type BrowserFido2Message = { sessionId: string } & ( } ); +export type BrowserFido2ParentWindowReference = chrome.tabs.Tab; + /** * Browser implementation of the {@link Fido2UserInterfaceService}. * The user interface is implemented as a popout and the service uses the browser's messaging API to communicate with it. */ -export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { +export class BrowserFido2UserInterfaceService + implements Fido2UserInterfaceServiceAbstraction +{ constructor(private authService: AuthService) {} async newSession( diff --git a/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts b/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts index 30dbd0b025e..c8bcf5faa4b 100644 --- a/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts +++ b/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CreateCredentialResult, AssertCredentialResult, diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index cc9ba61f4ee..c0be60f1cd0 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -1,3 +1,6 @@ +import { FieldRect } from "../background/abstractions/overlay.background"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AutofillFieldQualifierType } from "../enums/autofill-field.enums"; import { InlineMenuAccountCreationFieldTypes, @@ -122,4 +125,9 @@ export default class AutofillField { fieldQualifier?: AutofillFieldQualifierType; accountCreationFieldType?: InlineMenuAccountCreationFieldTypes; + + /** + * used for totp multiline calculations + */ + fieldRect?: FieldRect; } diff --git a/apps/browser/src/autofill/models/autofill-form.ts b/apps/browser/src/autofill/models/autofill-form.ts index 3f06e28a912..d335a81b3c4 100644 --- a/apps/browser/src/autofill/models/autofill-form.ts +++ b/apps/browser/src/autofill/models/autofill-form.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /** * Represents an HTML form whose elements can be autofilled */ diff --git a/apps/browser/src/autofill/models/autofill-page-details.ts b/apps/browser/src/autofill/models/autofill-page-details.ts index 89710e1597b..c32dfed4e43 100644 --- a/apps/browser/src/autofill/models/autofill-page-details.ts +++ b/apps/browser/src/autofill/models/autofill-page-details.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import AutofillField from "./autofill-field"; import AutofillForm from "./autofill-form"; diff --git a/apps/browser/src/autofill/models/autofill-script.ts b/apps/browser/src/autofill/models/autofill-script.ts index b9d6f1a1495..1da05e07308 100644 --- a/apps/browser/src/autofill/models/autofill-script.ts +++ b/apps/browser/src/autofill/models/autofill-script.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore // String values affect code flow in autofill.ts and must not be changed export type FillScriptActions = "click_on_opid" | "focus_by_opid" | "fill_by_opid"; diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 425d53783e1..2e38adacb32 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -1,7 +1,16 @@ import { Theme } from "@bitwarden/common/platform/enums"; +const NotificationTypes = { + Add: "add", + Change: "change", + Unlock: "unlock", + FilelessImport: "fileless-import", +} as const; + +type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; + type NotificationBarIframeInitData = { - type?: string; + type?: string; // @TODO use `NotificationType` isVaultLocked?: boolean; theme?: Theme; removeIndividualVault?: boolean; @@ -24,6 +33,8 @@ type NotificationBarWindowMessageHandlers = { }; export { + NotificationTypes, + NotificationType, NotificationBarIframeInitData, NotificationBarWindowMessage, NotificationBarWindowMessageHandlers, diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 3c625297318..2c0ebe8e8e7 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -13,6 +15,8 @@ import { NotificationBarIframeInitData, } from "./abstractions/notification-bar"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./bar.scss"); const logService = new ConsoleLogService(false); diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index da274291731..9bdaf0f965b 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { InlineMenuElementPosition, InlineMenuPosition, diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts index b13db89ff59..72bf631f50b 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts index 08f25fa09e2..4f497172b39 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts index 0ed14a520c1..36ef3897c56 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuButton } from "./autofill-inline-menu-button"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./button.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap index d920820b0e0..acd06fb8c65 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap @@ -817,6 +817,129 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f `; +exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the view for a totp field 1`] = ` +
+
    +
  • +
    + + +
    +
  • +
+
+`; + exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user creates the views for a list of card ciphers 1`] = `
`; +exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user renders correctly when there are multiple TOTP elements with username displayed 1`] = ` +
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
+`; + exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline menu for an unauthenticated user creates the views for the locked inline menu 1`] = `
{ expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); }); + it("creates the view for a totp field", async () => { + postWindowMessage( + createInitAutofillInlineMenuListMessageMock({ + inlineMenuFillType: CipherType.Login, + ciphers: [ + createAutofillOverlayCipherDataMock(1, { + type: CipherType.Login, + login: { + totp: "123456", + totpField: true, + }, + }), + ], + }), + ); + + await flushPromises(); + + const cipherSubtitleElement = autofillInlineMenuList[ + "inlineMenuListContainer" + ].querySelector('[data-testid="totp-code"]'); + + expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); + expect(cipherSubtitleElement).not.toBeNull(); + expect(cipherSubtitleElement.textContent).toBe("123 456"); + }); + + it("renders correctly when there are multiple TOTP elements with username displayed", async () => { + const totpCipher1 = createAutofillOverlayCipherDataMock(1, { + type: CipherType.Login, + login: { + totp: "123456", + totpField: true, + username: "user1", + }, + }); + + const totpCipher2 = createAutofillOverlayCipherDataMock(2, { + type: CipherType.Login, + login: { + totp: "654321", + totpField: true, + username: "user2", + }, + }); + + postWindowMessage( + createInitAutofillInlineMenuListMessageMock({ + inlineMenuFillType: CipherType.Login, + ciphers: [totpCipher1, totpCipher2], + }), + ); + + await flushPromises(); + const checkSubtitleElement = (username: string) => { + const subtitleElement = autofillInlineMenuList["inlineMenuListContainer"].querySelector( + `span.cipher-subtitle[title="${username}"]`, + ); + expect(subtitleElement).not.toBeNull(); + expect(subtitleElement.textContent).toBe(username); + }; + + checkSubtitleElement("user1"); + checkSubtitleElement("user2"); + + expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); + }); + it("creates the views for a list of card ciphers", () => { postWindowMessage( createInitAutofillInlineMenuListMessageMock({ diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts index 9e6aae3c29b..acb01594cc6 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts @@ -1,9 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EVENTS, UPDATE_PASSKEYS_HEADINGS_ON_SCROLL } from "@bitwarden/common/autofill/constants"; -import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { InlineMenuCipherData } from "../../../../background/abstractions/overlay.background"; import { InlineMenuFillTypes } from "../../../../enums/autofill-overlay.enum"; @@ -410,6 +412,29 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { ); } + /** + * Filters the ciphers to include only TOTP-related ones if the field is a TOTP field. + * If the field is a TOTP field but no TOTP is present, it returns an empty array. + * + * @param ciphers - The list of ciphers to filter. + * @returns The filtered list of ciphers or an empty list if no valid TOTP ciphers are present. + */ + private getFilteredCiphersForTotpField(ciphers: InlineMenuCipherData[]): InlineMenuCipherData[] { + if (!ciphers?.length) { + return []; + } + + const isTotpField = + this.inlineMenuFillType === CipherType.Login && + ciphers.some((cipher) => cipher.login?.totpField); + + if (isTotpField) { + return ciphers.filter((cipher) => cipher.login?.totp); + } + + return ciphers; + } + /** * Updates the list items with the passed ciphers. * If no ciphers are passed, the no results inline menu is built. @@ -425,12 +450,12 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return; } - this.ciphers = ciphers; + this.ciphers = this.getFilteredCiphersForTotpField(ciphers); this.currentCipherIndex = 0; this.showInlineMenuAccountCreation = showInlineMenuAccountCreation; this.resetInlineMenuContainer(); - if (!ciphers?.length) { + if (!this.ciphers?.length) { this.buildNoResultsInlineMenuList(); return; } @@ -1044,6 +1069,64 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { cipherIcon.classList.add("cipher-icon"); cipherIcon.setAttribute("aria-hidden", "true"); + if (cipher.login?.totpField && cipher.login?.totp) { + const totpContainer = document.createElement("div"); + totpContainer.style.position = "relative"; + + const svgElement = buildSvgDomElement(` + + + + + `); + + const [innerCircleElement, outerCircleElement] = svgElement.querySelectorAll("circle"); + innerCircleElement.classList.add("circle-color"); + outerCircleElement.classList.add("circle-color"); + + totpContainer.appendChild(svgElement); + + const totpSecondsSpan = document.createElement("span"); + totpSecondsSpan.classList.add("totp-sec-span"); + totpSecondsSpan.setAttribute("bitTypography", "helper"); + totpSecondsSpan.setAttribute("aria-label", this.getTranslation("totpSecondsSpanAria")); + totpContainer.appendChild(totpSecondsSpan); + + cipherIcon.appendChild(totpContainer); + + const intervalSeconds = cipher.login.totpCodeTimeInterval; + + const updateCountdown = () => { + const epoch = Math.round(Date.now() / 1000); + const mod = epoch % intervalSeconds; + const totpSeconds = intervalSeconds - mod; + + totpSecondsSpan.textContent = `${totpSeconds}`; + + /** + * Design specifies a seven-second time span as the period where expiry is approaching. + */ + const totpExpiryApproaching = totpSeconds <= 7; + + totpSecondsSpan.classList.toggle("totp-sec-span-danger", totpExpiryApproaching); + innerCircleElement.classList.toggle("circle-danger-color", totpExpiryApproaching); + outerCircleElement.classList.toggle("circle-danger-color", totpExpiryApproaching); + + innerCircleElement.style.strokeDashoffset = `${((intervalSeconds - totpSeconds) / intervalSeconds) * (2 * Math.PI * 12.5)}`; + + if (mod === 0) { + this.postMessageToParent({ command: "refreshOverlayCiphers" }); + } + }; + + updateCountdown(); + setInterval(updateCountdown, 1000); + + return cipherIcon; + } + if (cipher.icon?.image) { try { const url = new URL(cipher.icon.image); @@ -1102,6 +1185,9 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return this.buildPasskeysCipherDetailsElement(cipher, cipherDetailsElement); } + if (cipher.login?.totpField && cipher.login?.totp) { + return this.buildTotpElement(cipher.login?.totp, cipher.login?.username, cipher.reprompt); + } const subTitleText = this.getSubTitleText(cipher); const cipherSubtitleElement = this.buildCipherSubtitleElement(subTitleText); if (cipherSubtitleElement) { @@ -1111,6 +1197,59 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return cipherDetailsElement; } + /** + * Checks if there is more than one TOTP element being displayed. + * + * @returns {boolean} - Returns true if more than one TOTP element is displayed, otherwise false. + */ + private multipleTotpElements(): boolean { + return ( + this.ciphers.filter((cipher) => cipher.login?.totpField && cipher.login?.totp).length > 1 + ); + } + + /** + * Builds a TOTP element for a given TOTP code. + * + * @param totp - The TOTP code to display. + */ + + private buildTotpElement( + totpCode: string, + username: string, + reprompt: CipherRepromptType, + ): HTMLDivElement | null { + if (!totpCode) { + return null; + } + + const formattedTotpCode = `${totpCode.substring(0, 3)} ${totpCode.substring(3)}`; + + const containerElement = globalThis.document.createElement("div"); + containerElement.classList.add("cipher-details"); + const totpHeading = document.createElement("span"); + totpHeading.classList.add("cipher-name"); + totpHeading.textContent = this.getTranslation("fillVerificationCode"); + totpHeading.setAttribute("aria-label", this.getTranslation("fillVerificationCodeAria")); + + containerElement.appendChild(totpHeading); + + if (this.multipleTotpElements() && username) { + const usernameSubtitle = this.buildCipherSubtitleElement(username); + containerElement.appendChild(usernameSubtitle); + } + + const totpCodeSpan = document.createElement("span"); + totpCodeSpan.classList.toggle("cipher-subtitle"); + totpCodeSpan.classList.toggle("masked-totp", !!reprompt); + totpCodeSpan.textContent = reprompt ? "●●●●●●" : formattedTotpCode; + totpCodeSpan.setAttribute("aria-label", this.getTranslation("totpCodeAria")); + totpCodeSpan.setAttribute("data-testid", "totp-code"); + containerElement.appendChild(totpCodeSpan); + + return containerElement; + } + /** * Builds the name element for a given cipher. * diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts index c302c50b4a4..b46b208b084 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuList } from "./autofill-inline-menu-list"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./list.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss index 33fbc560f2b..d0875cfe427 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss @@ -350,6 +350,32 @@ body * { text-align: left; } + .totp-sec-span { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + @include themify($themes) { + color: themed("textColor"); + } + } + + .totp-sec-span-danger { + @include themify($themes) { + color: themed("passwordSpecialColor"); + } + } + .circle-color { + @include themify($themes) { + stroke: themed("primaryColor"); + } + } + .circle-danger-color { + @include themify($themes) { + stroke: themed("passwordSpecialColor"); + } + } + .cipher-name, .cipher-subtitle { display: block; @@ -378,6 +404,11 @@ body * { color: themed("mutedTextColor"); } + &.masked-totp { + font-size: 0.875rem; + letter-spacing: 0.2rem; + } + &--passkey { display: flex; align-content: center; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts index fafc23f9258..663eae9144a 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { setElementStyles } from "../../../../utils"; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts index 16d5c29d574..522b968e533 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./menu-container.scss"); import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container"; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts index 313b477a46a..950676cf202 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { RedirectFocusDirection } from "../../../../enums/autofill-overlay.enum"; diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index 98a820a3e5a..43a966dc4b6 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { NotificationBarIframeInitData } from "../../../notification/abstractions/notification-bar"; diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html deleted file mode 100644 index 852fd4a0e81..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html +++ /dev/null @@ -1,36 +0,0 @@ -
-
- -
-
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts deleted file mode 100644 index d9d492bdcc1..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core"; - -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; - -@Component({ - selector: "app-fido2-cipher-row-v1", - templateUrl: "fido2-cipher-row-v1.component.html", - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class Fido2CipherRowV1Component { - @Output() onSelected = new EventEmitter(); - @Input() cipher: CipherView; - @Input() last: boolean; - @Input() title: string; - @Input() isSearching: boolean; - @Input() isSelected: boolean; - - protected selectCipher(c: CipherView) { - this.onSelected.emit(c); - } - - /** - * Returns a subname for the cipher. - * If this has a FIDO2 credential, and the cipher.name is different from the FIDO2 credential's rpId, return the rpId. - * @param c Cipher - * @returns - */ - protected getSubName(c: CipherView): string | null { - const fido2Credentials = c.login?.fido2Credentials; - - if (!fido2Credentials || fido2Credentials.length === 0) { - return null; - } - - const [fido2Credential] = fido2Credentials; - - return c.name !== fido2Credential.rpId ? fido2Credential.rpId : null; - } -} 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 91bcd6494e6..98e73d2174c 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core"; diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html deleted file mode 100644 index 9f6c0aca50d..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - -
- -
-
- -
-
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts deleted file mode 100644 index cf79dfc6520..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition } from "@angular/cdk/overlay"; -import { Component } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { NeverDomains } from "@bitwarden/common/models/domain/domain-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 { fido2PopoutSessionData$ } from "../../../vault/popup/utils/fido2-popout-session-data"; -import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-fido2-user-interface.service"; - -@Component({ - selector: "app-fido2-use-browser-link-v1", - templateUrl: "fido2-use-browser-link-v1.component.html", - animations: [ - trigger("transformPanel", [ - state( - "void", - style({ - opacity: 0, - }), - ), - transition( - "void => open", - animate( - "100ms linear", - style({ - opacity: 1, - }), - ), - ), - transition("* => void", animate("100ms linear", style({ opacity: 0 }))), - ]), - ], -}) -export class Fido2UseBrowserLinkV1Component { - showOverlay = false; - isOpen = false; - overlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - offsetY: 5, - }, - ]; - - protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - private domainSettingsService: DomainSettingsService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - ) {} - - toggle() { - this.isOpen = !this.isOpen; - } - - close() { - this.isOpen = false; - } - - /** - * Aborts the current FIDO2 session and fallsback to the browser. - * @param excludeDomain - Identifies if the domain should be excluded from future FIDO2 prompts. - */ - protected async abort(excludeDomain = true) { - this.close(); - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - - if (!excludeDomain) { - this.abortSession(sessionData.sessionId); - return; - } - // Show overlay to prevent the user from interacting with the page. - this.showOverlay = true; - await this.handleDomainExclusion(sessionData.senderUrl); - // Give the user a chance to see the toast before closing the popout. - await Utils.delay(2000); - this.abortSession(sessionData.sessionId); - } - - /** - * Excludes the domain from future FIDO2 prompts. - * @param uri - The domain uri to exclude from future FIDO2 prompts. - */ - private async handleDomainExclusion(uri: string) { - const existingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - - const validDomain = Utils.getHostname(uri); - const savedDomains: NeverDomains = { - ...existingDomains, - }; - savedDomains[validDomain] = null; - - await this.domainSettingsService.setNeverDomains(savedDomains); - - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("domainAddedToExcludedDomains", validDomain), - ); - } - - private abortSession(sessionId: string) { - BrowserFido2UserInterfaceSession.abortPopout(sessionId, true); - } -} 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 86f13d29c7a..91d97ac96dc 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { animate, state, style, transition, trigger } from "@angular/animations"; import { A11yModule } from "@angular/cdk/a11y"; import { ConnectedPosition, CdkOverlayOrigin, CdkConnectedOverlay } from "@angular/cdk/overlay"; diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html deleted file mode 100644 index 8a052fbc5b7..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html +++ /dev/null @@ -1,142 +0,0 @@ - -
-
-
- - - - - - -
- - -
- -
-
-
- - - -
-

- {{ subtitleText | i18n }} -

- - -
-
- -
-
- -
- -
-
- - -
- -
-
-
-
- -
-

{{ "passkeyAlreadyExists" | i18n }}

-
-
- -
-
- -
-
- -
-

{{ "noPasskeysFoundForThisApplication" | i18n }}

-
- -
-
- - -
-
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts deleted file mode 100644 index d6026a8c7a0..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts +++ /dev/null @@ -1,443 +0,0 @@ -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { - BehaviorSubject, - combineLatest, - concatMap, - filter, - firstValueFrom, - map, - Observable, - Subject, - take, - takeUntil, -} from "rxjs"; - -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; -import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { DialogService } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { ZonedMessageListenerService } from "../../../platform/browser/zoned-message-listener.service"; -import { VaultPopoutType } from "../../../vault/popup/utils/vault-popout-window"; -import { Fido2UserVerificationService } from "../../../vault/services/fido2-user-verification.service"; -import { - BrowserFido2Message, - BrowserFido2UserInterfaceSession, - BrowserFido2MessageTypes, -} from "../../fido2/services/browser-fido2-user-interface.service"; - -interface ViewData { - message: BrowserFido2Message; - fallbackSupported: boolean; -} - -@Component({ - selector: "app-fido2-v1", - templateUrl: "fido2-v1.component.html", - styleUrls: [], -}) -export class Fido2V1Component implements OnInit, OnDestroy { - private destroy$ = new Subject(); - private hasSearched = false; - - protected cipher: CipherView; - protected searchTypeSearch = false; - protected searchPending = false; - protected searchText: string; - protected url: string; - protected hostname: string; - protected data$: Observable; - protected sessionId?: string; - protected senderTabId?: string; - protected ciphers?: CipherView[] = []; - protected displayedCiphers?: CipherView[] = []; - protected loading = false; - protected subtitleText: string; - protected credentialText: string; - protected BrowserFido2MessageTypes = BrowserFido2MessageTypes; - - private message$ = new BehaviorSubject(null); - - constructor( - private router: Router, - private activatedRoute: ActivatedRoute, - private cipherService: CipherService, - private platformUtilsService: PlatformUtilsService, - private domainSettingsService: DomainSettingsService, - private searchService: SearchService, - private logService: LogService, - private dialogService: DialogService, - private browserMessagingApi: ZonedMessageListenerService, - private passwordRepromptService: PasswordRepromptService, - private fido2UserVerificationService: Fido2UserVerificationService, - private accountService: AccountService, - ) {} - - ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - - const queryParams$ = this.activatedRoute.queryParamMap.pipe( - take(1), - map((queryParamMap) => ({ - sessionId: queryParamMap.get("sessionId"), - senderTabId: queryParamMap.get("senderTabId"), - senderUrl: queryParamMap.get("senderUrl"), - })), - ); - - combineLatest([ - queryParams$, - this.browserMessagingApi.messageListener$() as Observable, - ]) - .pipe( - concatMap(async ([queryParams, message]) => { - this.sessionId = queryParams.sessionId; - this.senderTabId = queryParams.senderTabId; - this.url = queryParams.senderUrl; - // For a 'NewSessionCreatedRequest', abort if it doesn't belong to the current session. - if ( - message.type === BrowserFido2MessageTypes.NewSessionCreatedRequest && - message.sessionId !== queryParams.sessionId - ) { - this.abort(false); - return; - } - - // Ignore messages that don't belong to the current session. - if (message.sessionId !== queryParams.sessionId) { - return; - } - - if (message.type === BrowserFido2MessageTypes.AbortRequest) { - this.abort(false); - return; - } - - return message; - }), - filter((message) => !!message), - takeUntil(this.destroy$), - ) - .subscribe((message) => { - this.message$.next(message); - }); - - this.data$ = this.message$.pipe( - filter((message) => message != undefined), - concatMap(async (message) => { - switch (message.type) { - case BrowserFido2MessageTypes.ConfirmNewCredentialRequest: { - const equivalentDomains = await firstValueFrom( - this.domainSettingsService.getUrlEquivalentDomains(this.url), - ); - - this.ciphers = (await this.cipherService.getAllDecrypted()).filter( - (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, - ); - this.displayedCiphers = this.ciphers.filter( - (cipher) => - cipher.login.matchesUri(this.url, equivalentDomains) && - this.hasNoOtherPasskeys(cipher, message.userHandle), - ); - - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } - break; - } - - case BrowserFido2MessageTypes.PickCredentialRequest: { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - this.ciphers = await Promise.all( - message.cipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); - }), - ); - this.displayedCiphers = [...this.ciphers]; - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } - break; - } - - case BrowserFido2MessageTypes.InformExcludedCredentialRequest: { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - this.ciphers = await Promise.all( - message.existingCipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); - }), - ); - this.displayedCiphers = [...this.ciphers]; - - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } - break; - } - } - - this.subtitleText = - this.displayedCiphers.length > 0 - ? this.getCredentialSubTitleText(message.type) - : "noMatchingPasskeyLogin"; - - this.credentialText = this.getCredentialButtonText(message.type); - return { - message, - fallbackSupported: "fallbackSupported" in message && message.fallbackSupported, - }; - }), - takeUntil(this.destroy$), - ); - - queryParams$.pipe(takeUntil(this.destroy$)).subscribe((queryParams) => { - this.send({ - sessionId: queryParams.sessionId, - type: BrowserFido2MessageTypes.ConnectResponse, - }); - }); - } - - protected async submit() { - const data = this.message$.value; - if (data?.type === BrowserFido2MessageTypes.PickCredentialRequest) { - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); - - this.send({ - sessionId: this.sessionId, - cipherId: this.cipher.id, - type: BrowserFido2MessageTypes.PickCredentialResponse, - userVerified, - }); - } else if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { - if (this.cipher.login.hasFido2Credentials) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "overwritePasskey" }, - content: { key: "overwritePasskeyAlert" }, - type: "info", - }); - - if (!confirmed) { - return false; - } - } - - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); - - this.send({ - sessionId: this.sessionId, - cipherId: this.cipher.id, - type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, - userVerified, - }); - } - - this.loading = true; - } - - protected async saveNewLogin() { - const data = this.message$.value; - if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { - const name = data.credentialName || data.rpId; - // TODO: Revert to check for user verification once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - await this.createNewCipher(name, data.userName); - - // We are bypassing user verification pending approval. - this.send({ - sessionId: this.sessionId, - cipherId: this.cipher?.id, - type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, - userVerified: data.userVerification, - }); - } - - this.loading = true; - } - - getCredentialSubTitleText(messageType: string): string { - return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest - ? "chooseCipherForPasskeySave" - : "logInWithPasskeyQuestion"; - } - - getCredentialButtonText(messageType: string): string { - return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest - ? "savePasskey" - : "confirm"; - } - - selectedPasskey(item: CipherView) { - this.cipher = item; - } - - viewPasskey() { - // 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(["/view-cipher"], { - queryParams: { - cipherId: this.cipher.id, - uilocation: "popout", - senderTabId: this.senderTabId, - sessionId: this.sessionId, - singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, - }, - }); - } - - addCipher() { - const data = this.message$.value; - - if (data?.type !== BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { - return; - } - - // 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(["/add-cipher"], { - queryParams: { - name: data.credentialName || data.rpId, - uri: this.url, - type: CipherType.Login.toString(), - uilocation: "popout", - username: data.userName, - senderTabId: this.senderTabId, - sessionId: this.sessionId, - userVerification: data.userVerification, - singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, - }, - }); - } - - protected async search() { - this.hasSearched = await this.searchService.isSearchable(this.searchText); - this.searchPending = true; - if (this.hasSearched) { - this.displayedCiphers = await this.searchService.searchCiphers( - this.searchText, - null, - this.ciphers, - ); - } else { - const equivalentDomains = await firstValueFrom( - this.domainSettingsService.getUrlEquivalentDomains(this.url), - ); - this.displayedCiphers = this.ciphers.filter((cipher) => - cipher.login.matchesUri(this.url, equivalentDomains), - ); - } - this.searchPending = false; - this.selectedPasskey(this.displayedCiphers[0]); - } - - abort(fallback: boolean) { - this.unload(fallback); - window.close(); - } - - unload(fallback = false) { - this.send({ - sessionId: this.sessionId, - type: BrowserFido2MessageTypes.AbortResponse, - fallbackRequested: fallback, - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - private buildCipher(name: string, username: string) { - this.cipher = new CipherView(); - this.cipher.name = name; - - this.cipher.type = CipherType.Login; - this.cipher.login = new LoginView(); - this.cipher.login.username = username; - this.cipher.login.uris = [new LoginUriView()]; - this.cipher.login.uris[0].uri = this.url; - this.cipher.card = new CardView(); - this.cipher.identity = new IdentityView(); - this.cipher.secureNote = new SecureNoteView(); - this.cipher.secureNote.type = SecureNoteType.Generic; - this.cipher.reprompt = CipherRepromptType.None; - } - - private async createNewCipher(name: string, username: string) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - this.buildCipher(name, username); - const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); - try { - await this.cipherService.createWithServer(cipher); - this.cipher.id = cipher.id; - } catch (e) { - this.logService.error(e); - } - } - - // TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production. - private async handleUserVerification( - userVerificationRequested: boolean, - cipher: CipherView, - ): Promise { - const masterPasswordRepromptRequired = cipher && cipher.reprompt !== 0; - - if (masterPasswordRepromptRequired) { - return await this.passwordRepromptService.showPasswordPrompt(); - } - - return userVerificationRequested; - } - - private send(msg: BrowserFido2Message) { - BrowserFido2UserInterfaceSession.sendMessage({ - sessionId: this.sessionId, - ...msg, - }); - } - - /** - * This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle - * @param userHandle - */ - private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean { - if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) { - return true; - } - - return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle); - } -} diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 82be95ea0da..4555d87f249 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormsModule } from "@angular/forms"; diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html deleted file mode 100644 index 530519e88f1..00000000000 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html +++ /dev/null @@ -1,258 +0,0 @@ -
-
- -
-

- {{ "autofill" | i18n }} -

-
-
-
-
-
- -
- -
-
-
-
- - -
- -
-
-
-
-
- - -
-
- - -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-

{{ "additionalOptions" | i18n }}

-
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts b/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts deleted file mode 100644 index 7879e4b343d..00000000000 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { - InlineMenuVisibilitySetting, - ClearClipboardDelaySetting, -} from "@bitwarden/common/autofill/types"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { - UriMatchStrategy, - UriMatchStrategySetting, -} from "@bitwarden/common/models/domain/domain-service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { DialogService } from "@bitwarden/components"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "app-autofill-v1", - templateUrl: "autofill-v1.component.html", -}) -export class AutofillV1Component implements OnInit { - protected canOverrideBrowserAutofillSetting = false; - protected defaultBrowserAutofillDisabled = false; - protected autoFillOverlayVisibility: InlineMenuVisibilitySetting; - protected autoFillOverlayVisibilityOptions: any[]; - protected disablePasswordManagerLink: string; - protected inlineMenuPositioningImprovementsEnabled: boolean = false; - protected showInlineMenuIdentities: boolean = true; - protected showInlineMenuCards: boolean = true; - inlineMenuIsEnabled: boolean = false; - enableAutoFillOnPageLoad = false; - autoFillOnPageLoadDefault = false; - autoFillOnPageLoadOptions: any[]; - enableContextMenuItem = false; - enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true? - clearClipboard: ClearClipboardDelaySetting; - clearClipboardOptions: any[]; - defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain; - uriMatchOptions: any[]; - showCardsCurrentTab = false; - showIdentitiesCurrentTab = false; - autofillKeyboardHelperText: string; - accountSwitcherEnabled = false; - - constructor( - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private domainSettingsService: DomainSettingsService, - private configService: ConfigService, - private dialogService: DialogService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private messagingService: MessagingService, - private vaultSettingsService: VaultSettingsService, - ) { - this.autoFillOverlayVisibilityOptions = [ - { - name: i18nService.t("autofillOverlayVisibilityOff"), - value: AutofillOverlayVisibility.Off, - }, - { - name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"), - value: AutofillOverlayVisibility.OnFieldFocus, - }, - { - name: i18nService.t("autofillOverlayVisibilityOnButtonClick"), - value: AutofillOverlayVisibility.OnButtonClick, - }, - ]; - this.autoFillOnPageLoadOptions = [ - { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, - { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, - ]; - this.clearClipboardOptions = [ - { name: i18nService.t("never"), value: null }, - { name: i18nService.t("tenSeconds"), value: 10 }, - { name: i18nService.t("twentySeconds"), value: 20 }, - { name: i18nService.t("thirtySeconds"), value: 30 }, - { name: i18nService.t("oneMinute"), value: 60 }, - { name: i18nService.t("twoMinutes"), value: 120 }, - { name: i18nService.t("fiveMinutes"), value: 300 }, - ]; - this.uriMatchOptions = [ - { name: i18nService.t("baseDomainOptionRecommended"), value: UriMatchStrategy.Domain }, - { name: i18nService.t("host"), value: UriMatchStrategy.Host }, - { name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith }, - { name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression }, - { name: i18nService.t("exact"), value: UriMatchStrategy.Exact }, - { name: i18nService.t("never"), value: UriMatchStrategy.Never }, - ]; - - this.accountSwitcherEnabled = enableAccountSwitching(); - this.disablePasswordManagerLink = this.getDisablePasswordManagerLink(); - } - - async ngOnInit() { - this.canOverrideBrowserAutofillSetting = - this.platformUtilsService.isChrome() || - this.platformUtilsService.isEdge() || - this.platformUtilsService.isOpera() || - this.platformUtilsService.isVivaldi(); - - this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden(); - - this.autoFillOverlayVisibility = await firstValueFrom( - this.autofillSettingsService.inlineMenuVisibility$, - ); - - this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.InlineMenuPositioningImprovements, - ); - - this.inlineMenuIsEnabled = this.isInlineMenuEnabled(); - - this.showInlineMenuIdentities = - this.inlineMenuPositioningImprovementsEnabled && - (await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$)); - - this.showInlineMenuCards = - this.inlineMenuPositioningImprovementsEnabled && - (await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$)); - - this.enableAutoFillOnPageLoad = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoad$, - ); - - this.autoFillOnPageLoadDefault = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoadDefault$, - ); - - this.enableContextMenuItem = await firstValueFrom( - this.autofillSettingsService.enableContextMenu$, - ); - - this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$); - - this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$); - - const defaultUriMatch = await firstValueFrom( - this.domainSettingsService.defaultUriMatchStrategy$, - ); - this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch; - - const command = await this.platformUtilsService.getAutofillKeyboardShortcut(); - await this.setAutofillKeyboardHelperText(command); - - this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$); - - this.showIdentitiesCurrentTab = await firstValueFrom( - this.vaultSettingsService.showIdentitiesCurrentTab$, - ); - } - - isInlineMenuEnabled() { - return ( - this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnFieldFocus || - this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnButtonClick - ); - } - - async updateAutoFillOverlayVisibility() { - await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility); - await this.requestPrivacyPermission(); - - this.inlineMenuIsEnabled = this.isInlineMenuEnabled(); - } - - async updateAutoFillOnPageLoad() { - await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad); - } - - async updateAutoFillOnPageLoadDefault() { - await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault); - } - - async saveDefaultUriMatch() { - await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch); - } - - private async setAutofillKeyboardHelperText(command: string) { - if (command) { - this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutText", command); - } else { - this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutNotSet"); - } - } - - async commandSettings() { - if (this.platformUtilsService.isChrome()) { - // 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 - BrowserApi.createNewTab("chrome://extensions/shortcuts"); - } else if (this.platformUtilsService.isOpera()) { - // 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 - BrowserApi.createNewTab("opera://extensions/shortcuts"); - } else if (this.platformUtilsService.isEdge()) { - // 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 - BrowserApi.createNewTab("edge://extensions/shortcuts"); - } else if (this.platformUtilsService.isVivaldi()) { - // 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 - BrowserApi.createNewTab("vivaldi://extensions/shortcuts"); - } else { - // 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 - BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts"); - } - } - - private getDisablePasswordManagerLink(): string { - if (this.platformUtilsService.isChrome()) { - return "chrome://settings/autofill"; - } - if (this.platformUtilsService.isOpera()) { - return "opera://settings/autofill"; - } - if (this.platformUtilsService.isEdge()) { - return "edge://settings/passwords"; - } - if (this.platformUtilsService.isVivaldi()) { - return "vivaldi://settings/autofill"; - } - - return "https://bitwarden.com/help/disable-browser-autofill/"; - } - - protected openDisablePasswordManagerLink(event: Event) { - event.preventDefault(); - // 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 - BrowserApi.createNewTab(this.disablePasswordManagerLink); - } - - async requestPrivacyPermission() { - if ( - this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off || - !this.canOverrideBrowserAutofillSetting || - (await this.browserAutofillSettingCurrentlyOverridden()) - ) { - return; - } - - await this.dialogService.openSimpleDialog({ - title: { key: "overrideDefaultBrowserAutofillTitle" }, - content: { key: "overrideDefaultBrowserAutofillDescription" }, - acceptButtonText: { key: "makeDefault" }, - acceptAction: async () => await this.handleOverrideDialogAccept(), - cancelButtonText: { key: "ignore" }, - type: "info", - }); - } - - async updateDefaultBrowserAutofillDisabled() { - const privacyPermissionGranted = await this.privacyPermissionGranted(); - if (!this.defaultBrowserAutofillDisabled && !privacyPermissionGranted) { - return; - } - - if ( - !privacyPermissionGranted && - !(await BrowserApi.requestPermission({ permissions: ["privacy"] })) - ) { - await this.dialogService.openSimpleDialog({ - title: { key: "privacyPermissionAdditionNotGrantedTitle" }, - content: { key: "privacyPermissionAdditionNotGrantedDescription" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "warning", - }); - this.defaultBrowserAutofillDisabled = false; - - return; - } - - BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled); - } - - private handleOverrideDialogAccept = async () => { - this.defaultBrowserAutofillDisabled = true; - 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"]); - } - - async updateContextMenuItem() { - await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem); - this.messagingService.send("bgUpdateContextMenu"); - } - - async updateAutoTotpCopy() { - await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy); - } - - async saveClearClipboard() { - await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard); - } - - async updateShowCardsCurrentTab() { - await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab); - } - - async updateShowIdentitiesCurrentTab() { - await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab); - } - - async updateShowInlineMenuCards() { - await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards); - } - - async updateShowInlineMenuIdentities() { - await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities); - } -} diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index e9c9fd9c75e..eeae0a85e3f 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -120,7 +120,7 @@

{{ "autofillSuggestionsSectionTitle" | i18n }}

/> {{ "showCardsInVaultView" | i18n }} - + {{ "autofillSuggestionsSectionTitle" | i18n }}

{{ "showIdentitiesInVaultView" | i18n }} + + + + {{ "clickToAutofillOnVault" | i18n }} + + @@ -160,7 +172,10 @@

{{ "enableAutoFillOnPageLoadSectionTitle" | i18n }}

{{ "enableAutoFillOnPageLoadDesc" | i18n }} - + {{ "warningCapitalized" | i18n }}: {{ "experimentalFeature" | i18n }} {{ "additionalOptions" | i18n }}
+ + + {{ "blockedDomains" | i18n }} + + + diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index ac247609b13..884503fa360 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { FormsModule } from "@angular/forms"; @@ -47,7 +49,6 @@ import { import { BrowserApi } from "../../../platform/browser/browser-api"; 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"; @@ -65,7 +66,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co JslibModule, LinkModule, PopOutComponent, - PopupFooterComponent, PopupHeaderComponent, PopupPageComponent, RouterModule, @@ -85,6 +85,7 @@ export class AutofillComponent implements OnInit { protected inlineMenuVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.OnFieldFocus; protected inlineMenuPositioningImprovementsEnabled: boolean = false; + protected blockBrowserInjectionsByDomainEnabled: boolean = false; protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown; protected disablePasswordManagerURI: DisablePasswordManagerUri = DisablePasswordManagerUris.Unknown; @@ -108,6 +109,7 @@ export class AutofillComponent implements OnInit { uriMatchOptions: { name: string; value: UriMatchStrategySetting }[]; showCardsCurrentTab: boolean = true; showIdentitiesCurrentTab: boolean = true; + clickItemsVaultView: boolean = false; autofillKeyboardHelperText: string; accountSwitcherEnabled: boolean = false; @@ -161,6 +163,10 @@ export class AutofillComponent implements OnInit { FeatureFlag.InlineMenuPositioningImprovements, ); + this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag( + FeatureFlag.BlockBrowserInjectionsByDomain, + ); + this.showInlineMenuIdentities = this.inlineMenuPositioningImprovementsEnabled && (await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$)); @@ -205,6 +211,10 @@ export class AutofillComponent implements OnInit { this.showIdentitiesCurrentTab = await firstValueFrom( this.vaultSettingsService.showIdentitiesCurrentTab$, ); + + this.clickItemsVaultView = await firstValueFrom( + this.vaultSettingsService.clickItemsToAutofillVaultView$, + ); } async updateInlineMenuVisibility() { @@ -411,4 +421,8 @@ export class AutofillComponent implements OnInit { async updateShowInlineMenuIdentities() { await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities); } + + async updateClickItemsVaultView() { + await this.vaultSettingsService.setClickItemsToAutofillVaultView(this.clickItemsVaultView); + } } diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.html b/apps/browser/src/autofill/popup/settings/blocked-domains.component.html new file mode 100644 index 00000000000..bf5f40f2b90 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.html @@ -0,0 +1,66 @@ + + + + + + + +
+

{{ "blockedDomainsDesc" | i18n }}

+ + +

{{ "domainsTitle" | i18n }}

+ {{ blockedDomainsState?.length || 0 }} +
+ + + + + {{ + "websiteItemLabel" | i18n: i + 1 + }} + +
{{ domain }}
+
+ +
+
+ +
+
+ + + +
diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts new file mode 100644 index 00000000000..461f62da6dc --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts @@ -0,0 +1,208 @@ +import { CommonModule } from "@angular/common"; +import { + QueryList, + Component, + ElementRef, + OnDestroy, + AfterViewInit, + ViewChildren, +} from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + ButtonModule, + CardComponent, + FormFieldModule, + IconButtonModule, + ItemModule, + LinkModule, + SectionComponent, + SectionHeaderComponent, + ToastService, + TypographyModule, +} 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({ + selector: "app-blocked-domains", + templateUrl: "blocked-domains.component.html", + standalone: true, + imports: [ + ButtonModule, + CardComponent, + CommonModule, + FormFieldModule, + FormsModule, + IconButtonModule, + ItemModule, + JslibModule, + LinkModule, + PopOutComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopupPageComponent, + RouterModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ], +}) +export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { + @ViewChildren("uriInput") uriInputElements: QueryList> = + new QueryList(); + + dataIsPristine = true; + isLoading = false; + blockedDomainsState: string[] = []; + storedBlockedDomains: string[] = []; + // How many fields should be non-editable before editable fields + fieldsEditThreshold: number = 0; + + private destroy$ = new Subject(); + + constructor( + private domainSettingsService: DomainSettingsService, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + async ngAfterViewInit() { + this.domainSettingsService.blockedInteractionsUris$ + .pipe(takeUntil(this.destroy$)) + .subscribe((neverDomains: NeverDomains) => this.handleStateUpdate(neverDomains)); + + this.uriInputElements.changes.pipe(takeUntil(this.destroy$)).subscribe(({ last }) => { + this.focusNewUriInput(last); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + handleStateUpdate(neverDomains: NeverDomains) { + if (neverDomains) { + this.storedBlockedDomains = Object.keys(neverDomains); + } + + this.blockedDomainsState = [...this.storedBlockedDomains]; + + // Do not allow the first x (pre-existing) fields to be edited + this.fieldsEditThreshold = this.storedBlockedDomains.length; + + this.dataIsPristine = true; + this.isLoading = false; + } + + focusNewUriInput(elementRef: ElementRef) { + if (elementRef?.nativeElement) { + elementRef.nativeElement.focus(); + } + } + + async addNewDomain() { + // add empty field to the Domains list interface + this.blockedDomainsState.push(""); + + await this.fieldChange(); + } + + async removeDomain(i: number) { + this.blockedDomainsState.splice(i, 1); + + // If a pre-existing field was dropped, lower the edit threshold + if (i < this.fieldsEditThreshold) { + this.fieldsEditThreshold--; + } + + await this.fieldChange(); + } + + async fieldChange() { + if (this.dataIsPristine) { + this.dataIsPristine = false; + } + } + + async saveChanges() { + if (this.dataIsPristine) { + return; + } + + this.isLoading = true; + + const newBlockedDomainsSaveState: NeverDomains = {}; + const uniqueBlockedDomains = new Set(this.blockedDomainsState); + + for (const uri of uniqueBlockedDomains) { + if (uri && uri !== "") { + const validatedHost = Utils.getHostname(uri); + + if (!validatedHost) { + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsInvalidDomain", uri), + title: "", + variant: "error", + }); + + // Don't reset via `handleStateUpdate` to allow existing input value correction + this.isLoading = false; + return; + } + + newBlockedDomainsSaveState[validatedHost] = null; + } + } + + try { + const existingState = new Set(this.storedBlockedDomains); + const newState = new Set(Object.keys(newBlockedDomainsSaveState)); + const stateIsUnchanged = + existingState.size === newState.size && + new Set([...existingState, ...newState]).size === existingState.size; + + // The subscriber updates don't trigger if `setNeverDomains` sets an equivalent state + if (stateIsUnchanged) { + // Reset UI state directly + const constructedNeverDomainsState = this.storedBlockedDomains.reduce( + (neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }), + {}, + ); + this.handleStateUpdate(constructedNeverDomainsState); + } else { + await this.domainSettingsService.setBlockedInteractionsUris(newBlockedDomainsSaveState); + } + + this.toastService.showToast({ + message: this.i18nService.t("blockedDomainsSavedSuccess"), + title: "", + variant: "success", + }); + } catch { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + title: "", + variant: "error", + }); + + // Don't reset via `handleStateUpdate` to preserve input values + this.isLoading = false; + } + } + + trackByFunction(index: number, _: string) { + return index; + } +} diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html deleted file mode 100644 index 8f78ac16404..00000000000 --- a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html +++ /dev/null @@ -1,91 +0,0 @@ - -
-
- -
-

- {{ "excludedDomains" | i18n }} -

-
- -
-
-
-
-
- - -
- -
- - - - -
-
- -
-
-
- -
-
-
- diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts deleted file mode 100644 index 362ac4896c2..00000000000 --- a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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 { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; - -interface ExcludedDomain { - uri: string; - showCurrentUris: boolean; -} - -const BroadcasterSubscriptionId = "excludedDomains"; - -@Component({ - selector: "app-excluded-domains-v1", - templateUrl: "excluded-domains-v1.component.html", -}) -export class ExcludedDomainsV1Component implements OnInit, OnDestroy { - excludedDomains: ExcludedDomain[] = []; - existingExcludedDomains: ExcludedDomain[] = []; - currentUris: string[]; - loadCurrentUrisTimeout: number; - accountSwitcherEnabled = false; - - constructor( - private domainSettingsService: DomainSettingsService, - private i18nService: I18nService, - private router: Router, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - private platformUtilsService: PlatformUtilsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - if (savedDomains) { - for (const uri of Object.keys(savedDomains)) { - this.excludedDomains.push({ uri: uri, showCurrentUris: false }); - this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false }); - } - } - - await this.loadCurrentUris(); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "tabChanged": - case "windowChanged": - if (this.loadCurrentUrisTimeout != null) { - window.clearTimeout(this.loadCurrentUrisTimeout); - } - this.loadCurrentUrisTimeout = window.setTimeout( - async () => await this.loadCurrentUris(), - 500, - ); - break; - default: - break; - } - }); - }); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async addUri() { - this.excludedDomains.push({ uri: "", showCurrentUris: false }); - } - - async removeUri(i: number) { - this.excludedDomains.splice(i, 1); - } - - async submit() { - const savedDomains: { [name: string]: null } = {}; - const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains); - for (const domain of this.excludedDomains) { - const resp = newExcludedDomains.filter((e) => e.uri === domain.uri); - if (resp.length === 0) { - savedDomains[domain.uri] = null; - } else { - if (domain.uri && domain.uri !== "") { - const validDomain = Utils.getHostname(domain.uri); - if (!validDomain) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", domain.uri), - ); - return; - } - savedDomains[validDomain] = null; - } - } - } - - await this.domainSettingsService.setNeverDomains(savedDomains); - // 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(["/tabs/settings"]); - } - - trackByFunction(index: number, item: any) { - return index; - } - - getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] { - const result = this.excludedDomains.filter( - (newDomain) => - !this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri), - ); - return result; - } - - toggleUriInput(domain: ExcludedDomain) { - domain.showCurrentUris = !domain.showCurrentUris; - } - - async loadCurrentUris() { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - if (tabs) { - const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); - uriSet.delete(null); - this.currentUris = Array.from(uriSet); - } - } -} 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 f622312ce04..7d429bfe4f0 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -15,7 +15,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-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 { ButtonModule, @@ -26,6 +25,7 @@ import { LinkModule, SectionComponent, SectionHeaderComponent, + ToastService, TypographyModule, } from "@bitwarden/components"; @@ -60,7 +60,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ], }) export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { - @ViewChildren("uriInput") uriInputElements: QueryList>; + @ViewChildren("uriInput") uriInputElements: QueryList> = + new QueryList(); accountSwitcherEnabled = false; dataIsPristine = true; @@ -75,7 +76,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { constructor( private domainSettingsService: DomainSettingsService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) { this.accountSwitcherEnabled = enableAccountSwitching(); } @@ -154,11 +155,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { const validatedHost = Utils.getHostname(uri); if (!validatedHost) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", uri), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsInvalidDomain", uri), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to allow existing input value correction this.isLoading = false; @@ -180,7 +181,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { if (stateIsUnchanged) { // Reset UI state directly const constructedNeverDomainsState = this.storedExcludedDomains.reduce( - (neverDomains, uri) => ({ ...neverDomains, [uri]: null }), + (neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }), {}, ); this.handleStateUpdate(constructedNeverDomainsState); @@ -188,13 +189,17 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState); } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("excludedDomainsSavedSuccess"), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsSavedSuccess"), + title: "", + variant: "success", + }); } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to preserve input values this.isLoading = false; diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html deleted file mode 100644 index 89d83c9e480..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html +++ /dev/null @@ -1,89 +0,0 @@ -
-
- -
-

- {{ "notifications" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
- -
-
-
diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts deleted file mode 100644 index 442e91e55eb..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; - -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "autofill-notification-v1-settings", - templateUrl: "notifications-v1.component.html", -}) -export class NotificationsSettingsV1Component implements OnInit { - enableAddLoginNotification = false; - enableChangedPasswordNotification = false; - enablePasskeys = true; - accountSwitcherEnabled = false; - - constructor( - private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, - private vaultSettingsService: VaultSettingsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableAddLoginNotification = await firstValueFrom( - this.userNotificationSettingsService.enableAddedLoginPrompt$, - ); - - this.enableChangedPasswordNotification = await firstValueFrom( - this.userNotificationSettingsService.enableChangedPasswordPrompt$, - ); - - this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); - } - - async updateAddLoginNotification() { - await this.userNotificationSettingsService.setEnableAddedLoginPrompt( - this.enableAddLoginNotification, - ); - } - - async updateChangedPasswordNotification() { - await this.userNotificationSettingsService.setEnableChangedPasswordPrompt( - this.enableChangedPasswordNotification, - ); - } - - async updateEnablePasskeys() { - await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys); - } -} diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html index 86fe4923df8..c6446012d0c 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.html +++ b/apps/browser/src/autofill/popup/settings/notifications.component.html @@ -50,7 +50,7 @@

{{ "vaultSaveOptionsTitle" | i18n }}

{{ "excludedDomains" | i18n }} - + diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 378f9eaddf1..5b1b4b3b8bb 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UriMatchStrategySetting } from "@bitwarden/common/models/domain/domain-service"; diff --git a/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts b/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts index c0f721996f4..04033664e9a 100644 --- a/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts +++ b/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts @@ -45,4 +45,5 @@ export interface InlineMenuFieldQualificationService { isFieldForIdentityUsername(field: AutofillField): boolean; isElementLoginSubmitButton(element: Element): boolean; isElementChangePasswordSubmitButton(element: Element): boolean; + isTotpField(field: AutofillField): boolean; } 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 511e5dd594b..acf0dedde27 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { FocusableElement, tabbable } from "tabbable"; @@ -955,8 +957,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ accountCreationFieldType: autofillFieldData?.accountCreationFieldType, }; + const allFields = this.formFieldElements; + const allFieldsRect = []; + + for (const key of allFields.keys()) { + const rect = await this.getMostRecentlyFocusedFieldRects(key); + allFieldsRect.push({ ...allFields.get(key), rect }); // Add the combined result to the array + } + await this.sendExtensionMessage("updateFocusedFieldData", { focusedFieldData: this.focusedFieldData, + allFieldsRect, }); } @@ -1406,6 +1417,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ url.origin + pathWithoutTrailingSlashAndSearch, url.origin + pathWithoutTrailingSlashSearchAndHash, ]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_error) { return null; } diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 0b036e17a30..16b11b98866 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -1,4 +1,4 @@ -import { mock, mockReset, MockProxy } from "jest-mock-extended"; +import { mock, MockProxy, mockReset } from "jest-mock-extended"; import { BehaviorSubject, of, Subject } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -98,7 +98,13 @@ describe("AutofillService", () => { let messageListener: MockProxy; beforeEach(() => { - scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(false)); + scriptInjectorService = new BrowserScriptInjectorService( + domainSettingsService, + platformUtilsService, + logService, + ); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus); showInlineMenuCardsMock$ = new BehaviorSubject(false); showInlineMenuIdentitiesMock$ = new BehaviorSubject(false); @@ -106,10 +112,10 @@ describe("AutofillService", () => { autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$; autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$; autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$; + autofillSettingsService.autofillOnPageLoad$ = of(true); activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; - configService = mock(); messageListener = mock(); enableChangedPasswordPromptMock$ = new BehaviorSubject(true); enableAddedLoginPromptMock$ = new BehaviorSubject(true); @@ -132,7 +138,7 @@ describe("AutofillService", () => { userNotificationsSettings, messageListener, ); - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); jest.spyOn(BrowserApi, "tabSendMessage"); }); @@ -143,7 +149,7 @@ describe("AutofillService", () => { }); describe("collectPageDetailsFromTab$", () => { - const tab = mock({ id: 1 }); + const tab = mock({ id: 1, url: "https://www.example.com" }); const messages = new Subject(); function mockCollectPageDetailsResponseMessage( @@ -165,11 +171,16 @@ describe("AutofillService", () => { it("sends a `collectPageDetails` message to the passed tab", () => { autofillService.collectPageDetailsFromTab$(tab); - expect(BrowserApi.tabSendMessage).toHaveBeenCalledWith(tab, { - command: AutofillMessageCommand.collectPageDetails, - sender: AutofillMessageSender.collectPageDetailsFromTabObservable, + expect(BrowserApi.tabSendMessage).toHaveBeenCalledWith( tab, - }); + { + command: AutofillMessageCommand.collectPageDetails, + sender: AutofillMessageSender.collectPageDetailsFromTabObservable, + tab, + }, + null, + true, + ); }); it("builds an array of page details from received `collectPageDetailsResponse` messages", async () => { @@ -218,6 +229,41 @@ describe("AutofillService", () => { expect(tracker.emissions[1]).toBeUndefined(); }); + + it("returns an empty array when the tab.url is empty", async () => { + const tracker = subscribeTo(autofillService.collectPageDetailsFromTab$({ ...tab, url: "" })); + + await tracker.pauseUntilReceived(1); + + expect(tracker.emissions[0]).toEqual([]); + }); + + it("returns an empty array when the `BrowserApi.tabSendMessage` throws an error", async () => { + (BrowserApi.tabSendMessage as jest.Mock).mockRejectedValueOnce(undefined); + + const tracker = subscribeTo(autofillService.collectPageDetailsFromTab$(tab)); + + await tracker.pauseUntilReceived(1); + + expect(tracker.emissions[0]).toEqual([]); + }); + + ["moz-extension://", "chrome-extension://", "safari-web-extension://"].forEach( + (extensionPrefix) => { + it(`returns an empty array when the tab.url starts with ${extensionPrefix}`, async () => { + const tracker = subscribeTo( + autofillService.collectPageDetailsFromTab$({ + ...tab, + url: `${extensionPrefix}/3e42342/popup/index.html`, + }), + ); + + await tracker.pauseUntilReceived(1); + + expect(tracker.emissions[0]).toEqual([]); + }); + }, + ); }); describe("loadAutofillScriptsOnInstall", () => { @@ -345,6 +391,7 @@ describe("AutofillService", () => { ); tabMock = createChromeTabMock(); sender = { tab: tabMock, frameId: 1 }; + jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); jest .spyOn(autofillService, "getInlineMenuVisibility") @@ -683,7 +730,9 @@ describe("AutofillService", () => { it("throws an error if an autofill did not occur for any of the passed pages", async () => { autofillOptions.tab.url = "https://a-different-url.com"; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(true)); try { await autofillService.doAutoFill(autofillOptions); @@ -865,7 +914,9 @@ describe("AutofillService", () => { it("returns a TOTP value", async () => { const totpCode = "123456"; autofillOptions.cipher.login.totp = "totp"; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(true)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode); @@ -878,7 +929,9 @@ describe("AutofillService", () => { it("does not return a TOTP value if the user does not have premium features", async () => { autofillOptions.cipher.login.totp = "totp"; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(false)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); const autofillResult = await autofillService.doAutoFill(autofillOptions); @@ -912,7 +965,9 @@ describe("AutofillService", () => { it("returns a null value if the user cannot access premium and the organization does not use TOTP", async () => { autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.organizationUseTotp = false; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(false)); const autofillResult = await autofillService.doAutoFill(autofillOptions); @@ -922,7 +977,9 @@ describe("AutofillService", () => { it("returns a null value if the user has disabled `auto TOTP copy`", async () => { autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.organizationUseTotp = true; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(true)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false); jest.spyOn(totpService, "getCode"); @@ -2897,7 +2954,9 @@ describe("AutofillService", () => { const expectedDateFormats = [ ["mm/yyyy", "05/2024"], + ["mm/YYYY", "05/2024"], ["mm/yy", "05/24"], + ["MM/YY", "05/24"], ["yyyy/mm", "2024/05"], ["yy/mm", "24/05"], ["mm-yyyy", "05-2024"], @@ -2906,6 +2965,7 @@ describe("AutofillService", () => { ["yy-mm", "24-05"], ["yyyymm", "202405"], ["yymm", "2405"], + ["YYMM", "2405"], ["mmyyyy", "052024"], ["mmyy", "0524"], ]; @@ -3835,7 +3895,7 @@ describe("AutofillService", () => { }); describe("given a autofill field value that indicates the field is a `select` input", () => { - it("will not add an autofil action to the fill script if the dataValue cannot be found in the select options", () => { + it("will not add an autofill action to the fill script if the dataValue cannot be found in the select options", () => { const dataValue = "username"; const selectField = createAutofillFieldMock({ opid: "username-field", diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index e79f6f69a36..6d0e9954ade 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1,4 +1,6 @@ -import { filter, firstValueFrom, Observable, scan, startWith } from "rxjs"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { filter, firstValueFrom, merge, Observable, ReplaySubject, scan, startWith } from "rxjs"; import { pairwise } from "rxjs/operators"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -91,6 +93,9 @@ export default class AutofillService implements AutofillServiceInterface { * @param tab The tab to collect page details from */ collectPageDetailsFromTab$(tab: chrome.tabs.Tab): Observable { + /** Replay Subject that can be utilized when `messages$` may not emit the page details. */ + const pageDetailsFallback$ = new ReplaySubject<[]>(1); + const pageDetailsFromTab$ = this.messageListener .messages$(COLLECT_PAGE_DETAILS_RESPONSE_COMMAND) .pipe( @@ -112,13 +117,35 @@ export default class AutofillService implements AutofillServiceInterface { ), ); - void BrowserApi.tabSendMessage(tab, { - tab: tab, - command: AutofillMessageCommand.collectPageDetails, - sender: AutofillMessageSender.collectPageDetailsFromTabObservable, + void BrowserApi.tabSendMessage( + tab, + { + tab: tab, + command: AutofillMessageCommand.collectPageDetails, + sender: AutofillMessageSender.collectPageDetailsFromTabObservable, + }, + null, + true, + ).catch(() => { + // When `tabSendMessage` throws an error the `pageDetailsFromTab$` will not emit, + // fallback to an empty array + pageDetailsFallback$.next([]); }); - return pageDetailsFromTab$; + // Fallback to empty array when: + // - In Safari, `tabSendMessage` doesn't throw an error for this case. + // - When opening the extension directly via the URL, `tabSendMessage` doesn't always respond nor throw an error in FireFox. + // Adding checks for the major 3 browsers here to be safe. + const urlHasBrowserProtocol = [ + "moz-extension://", + "chrome-extension://", + "safari-web-extension://", + ].some((protocol) => tab.url.startsWith(protocol)); + if (!tab.url || urlHasBrowserProtocol) { + pageDetailsFallback$.next([]); + } + + return merge(pageDetailsFromTab$, pageDetailsFallback$); } /** @@ -389,8 +416,9 @@ export default class AutofillService implements AutofillServiceInterface { let totp: string | null = null; + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeAccount.id), ); const defaultUriMatch = await this.getDefaultUriMatchStrategy(); @@ -1487,7 +1515,7 @@ export default class AutofillService implements AutofillServiceInterface { ); return CreditCardAutoFillConstants.CardAttributesExtended.find((attributeName) => { - const fieldAttributeValue = field[attributeName]; + const fieldAttributeValue = field[attributeName]?.toLocaleLowerCase(); const fieldAttributeMatch = fieldAttributeValue?.match(dateFormatPattern); // break find as soon as a match is found @@ -1597,16 +1625,6 @@ export default class AutofillService implements AutofillServiceInterface { ) { fillFields.email = f; break; - } else if ( - !fillFields.address && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.AddressFieldNames, - IdentityAutoFillConstants.AddressFieldNameValues, - ) - ) { - fillFields.address = f; - break; } else if ( !fillFields.address1 && AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) @@ -1625,6 +1643,16 @@ export default class AutofillService implements AutofillServiceInterface { ) { 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) @@ -1818,11 +1846,6 @@ export default class AutofillService implements AutofillServiceInterface { continue; } - if (this.shouldMakeIdentityAddressFillScript(filledFields, keywordsList)) { - this.makeIdentityAddressFillScript(fillScript, filledFields, field, identity); - continue; - } - if (this.shouldMakeIdentityAddress1FillScript(filledFields, keywordsCombined)) { this.makeScriptActionWithValue(fillScript, identity.address1, field, filledFields); continue; @@ -1838,6 +1861,11 @@ export default class AutofillService implements AutofillServiceInterface { continue; } + if (this.shouldMakeIdentityAddressFillScript(filledFields, keywordsList)) { + this.makeIdentityAddressFillScript(fillScript, filledFields, field, identity); + continue; + } + if (this.shouldMakeIdentityPostalCodeFillScript(filledFields, keywordsCombined)) { this.makeScriptActionWithValue(fillScript, identity.postalCode, field, filledFields); continue; diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 94d84997ee5..13c546bccdb 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import AutofillField from "../models/autofill-field"; import AutofillForm from "../models/autofill-form"; import AutofillPageDetails from "../models/autofill-page-details"; diff --git a/apps/browser/src/autofill/services/dom-element-visibility.service.ts b/apps/browser/src/autofill/services/dom-element-visibility.service.ts index 9dad496847b..bd75cb55ba5 100644 --- a/apps/browser/src/autofill/services/dom-element-visibility.service.ts +++ b/apps/browser/src/autofill/services/dom-element-visibility.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service"; import { FillableFormFieldElement, FormFieldElement } from "../types"; diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index 0131b16da91..16310397a03 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS, MAX_DEEP_QUERY_RECURSION_DEPTH } from "@bitwarden/common/autofill/constants"; import { nodeIsElement, sendExtensionMessage } from "../utils"; @@ -233,6 +235,8 @@ export class DomQueryService implements DomQueryServiceInterface { if ((chrome as any).dom?.openOrClosedShadowRoot) { try { return (chrome as any).dom.openOrClosedShadowRoot(node); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts index 0f7c966de3e..dc59e05a18b 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts @@ -21,7 +21,41 @@ describe("InlineMenuFieldQualificationService", () => { }); describe("isFieldForLoginForm", () => { - it("disqualifies totp fields", () => { + it("does not disqualify totp fields for premium users with flag set to true", () => { + inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true; + inlineMenuFieldQualificationService["premiumEnabled"] = true; + const field = mock({ + type: "text", + autoCompleteType: "one-time-code", + htmlName: "totp", + htmlID: "totp", + placeholder: "totp", + }); + + expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe( + true, + ); + }); + + it("disqualifies totp fields for premium users with flag set to false", () => { + inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = false; + inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true; + const field = mock({ + type: "text", + autoCompleteType: "one-time-code", + htmlName: "totp", + htmlID: "totp", + placeholder: "totp", + }); + + expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe( + false, + ); + }); + + it("disqualifies totp fields for non-premium users with flag set to true", () => { + inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true; + inlineMenuFieldQualificationService["premiumEnabled"] = false; const field = mock({ type: "text", autoCompleteType: "one-time-code", diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index fe5410da730..43efd338c6e 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import AutofillField from "../models/autofill-field"; import AutofillPageDetails from "../models/autofill-page-details"; import { getSubmitButtonKeywordsSet, sendExtensionMessage } from "../utils"; @@ -148,12 +150,19 @@ export class InlineMenuFieldQualificationService ]); private totpFieldAutocompleteValue = "one-time-code"; private inlineMenuFieldQualificationFlagSet = false; + private inlineMenuTotpFeatureFlag = false; + private premiumEnabled = false; constructor() { - void sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag").then( - (getInlineMenuFieldQualificationFlag) => - (this.inlineMenuFieldQualificationFlagSet = !!getInlineMenuFieldQualificationFlag?.result), - ); + void Promise.all([ + sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag"), + sendExtensionMessage("getInlineMenuTotpFeatureFlag"), + sendExtensionMessage("getUserPremiumStatus"), + ]).then(([fieldQualificationFlag, totpFeatureFlag, premiumStatus]) => { + this.inlineMenuFieldQualificationFlagSet = !!fieldQualificationFlag?.result; + this.inlineMenuTotpFeatureFlag = !!totpFeatureFlag?.result; + this.premiumEnabled = !!premiumStatus?.result; + }); } /** @@ -167,8 +176,16 @@ export class InlineMenuFieldQualificationService return this.isFieldForLoginFormFallback(field); } - if (this.isTotpField(field)) { - return false; + /** + * Totp inline menu is available only for premium users. + */ + if (this.inlineMenuTotpFeatureFlag && this.premiumEnabled) { + const isTotpField = this.isTotpField(field); + // Autofill does not fill totp inputs with a "password" `type` attribute value + const passwordType = field.type === "password"; + if (isTotpField && !passwordType) { + return true; + } } const isCurrentPasswordField = this.isCurrentPasswordField(field); @@ -985,7 +1002,7 @@ export class InlineMenuFieldQualificationService * * @param field - The field to validate */ - private isTotpField = (field: AutofillField): boolean => { + isTotpField = (field: AutofillField): boolean => { if (this.fieldContainsAutocompleteValues(field, this.totpFieldAutocompleteValue)) { return true; } diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.ts index 813baa5922f..6034563a947 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EVENTS, TYPE_CHECK } from "@bitwarden/common/autofill/constants"; import AutofillScript, { AutofillInsertActions, FillScript } from "../models/autofill-script"; diff --git a/apps/browser/src/autofill/shared/styles/variables.scss b/apps/browser/src/autofill/shared/styles/variables.scss index 208e2bef0a3..12d55ad8be6 100644 --- a/apps/browser/src/autofill/shared/styles/variables.scss +++ b/apps/browser/src/autofill/shared/styles/variables.scss @@ -2,23 +2,37 @@ $dark-icon-themes: "theme_dark", "theme_solarizedDark", "theme_nord"; -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-source-code-pro: "Source Code Pro", monospace; $font-size-base: 14px; -$text-color: #212529; -$muted-text-color: #6c747c; -$border-color: #ced4dc; + +$text-color-light: #212529; +$muted-text-color-light: #6c747c; +$background-color-light: #ffffff; +$background-offset-color-light: #f0f0f0; +$brand-primary-light: #175ddc; +$password-special-color-light: #b80017; +$password-number-color-light: #1452c1; +$success-color-light: #017e45; +$error-color-light: #c83522; + +$text-color-dark: #ffffff; +$muted-text-color-dark: #bac0ce; +$background-color-dark: #2f343d; +$background-offset-color-dark: darken(#2f343d, 2.75%); $border-color-dark: #ddd; -$border-radius: 3px; -$focus-outline-color: #1252a3; -$muted-blue: #5a6d91; -$password-special-color: #b80017; -$password-number-color: #1452c1; +$brand-primary-dark: #6f9df1; +$password-special-color-dark: #ff8d85; +$password-number-color-dark: #6f9df1; +$success-color-dark: #8db89b; +$error-color-dark: #ee9792; -$brand-primary: #175ddc; +$muted-blue: #5a6d91; +$muted-grey: #bac0ce; +$border-color: #ced4dc; -$background-color: #ffffff; -$background-offset-color: #f0f0f0; +$border-radius: 3px; +$focus-outline-color: #1252a3; $solarizedDarkBase0: #839496; $solarizedDarkBase03: #002b36; @@ -28,48 +42,42 @@ $solarizedDarkBase2: #eee8d5; $solarizedDarkCyan: #2aa198; $solarizedDarkGreen: #859900; -$success-color-light: #017e45; -$success-color-dark: #8db89b; - -$error-color-light: #c83522; -$error-color-dark: #ee9792; - $themes: ( light: ( - textColor: $text-color, - mutedTextColor: $muted-text-color, - backgroundColor: $background-color, - backgroundOffsetColor: $background-offset-color, - primaryColor: $brand-primary, - buttonPrimaryColor: $brand-primary, - textContrast: $background-color, + textColor: $text-color-light, + mutedTextColor: $muted-text-color-light, + backgroundColor: $background-color-light, + backgroundOffsetColor: $background-offset-color-light, + primaryColor: $brand-primary-light, + buttonPrimaryColor: $brand-primary-light, + textContrast: $background-color-light, inputBorderColor: darken($border-color-dark, 2.75%), - inputBackgroundColor: #ffffff, + inputBackgroundColor: $background-color-light, borderColor: $border-color, focusOutlineColor: $focus-outline-color, successColor: $success-color-light, errorColor: $error-color-light, passkeysAuthenticating: $muted-blue, - passwordSpecialColor: $password-special-color, - passwordNumberColor: $password-number-color, + passwordSpecialColor: $password-special-color-light, + passwordNumberColor: $password-number-color-light, ), dark: ( - textColor: #ffffff, - mutedTextColor: #bac0ce, - backgroundColor: #2f343d, - backgroundOffsetColor: darken(#2f343d, 2.75%), - buttonPrimaryColor: #6f9df1, - primaryColor: #6f9df1, - textContrast: #2f343d, + textColor: $text-color-dark, + mutedTextColor: $muted-text-color-dark, + backgroundColor: $background-color-dark, + backgroundOffsetColor: $background-offset-color-dark, + buttonPrimaryColor: $brand-primary-dark, + primaryColor: $brand-primary-dark, + textContrast: $background-color-dark, inputBorderColor: #4c525f, - inputBackgroundColor: #2f343d, + inputBackgroundColor: $background-color-dark, borderColor: #4c525f, focusOutlineColor: lighten($focus-outline-color, 25%), successColor: $success-color-dark, errorColor: $error-color-dark, - passkeysAuthenticating: #bac0ce, - passwordSpecialColor: #ff8d85, - passwordNumberColor: #6f9df1, + passkeysAuthenticating: $muted-grey, + passwordSpecialColor: $password-special-color-dark, + passwordNumberColor: $password-number-color-dark, ), nord: ( textColor: $nord5, diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index a4b6d700090..6e175906d30 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; diff --git a/apps/browser/src/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index a14ef1330cc..58ac95e7edf 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -1,5 +1,5 @@ +import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; -import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum"; import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; import { CipherType } from "@bitwarden/common/vault/enums"; diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 598e3bc3efe..0e102dcfd99 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -1,3 +1,6 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { FieldRect } from "../background/abstractions/overlay.background"; import { AutofillPort } from "../enums/autofill-port.enum"; import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types"; @@ -542,3 +545,31 @@ export const specialCharacterToKeyMap: Record = { "?": "questionCharacterDescriptor", "/": "forwardSlashCharacterDescriptor", }; + +/** + * Determines if the current rect values are not all 0. + */ +export function rectHasSize(rect: FieldRect): boolean { + if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) { + return true; + } + + return false; +} + +/** + * Checks if all the values corresponding to the specified keys in an object are null. + * If no keys are specified, checks all keys in the object. + * + * @param obj - The object to check. + * @param keys - An optional array of keys to check in the object. Defaults to all keys. + * @returns Returns true if all values for the specified keys (or all keys if none are provided) are null; otherwise, false. + */ +export function areKeyValuesNull>( + obj: T, + keys?: Array, +): boolean { + const keysToCheck = keys && keys.length > 0 ? keys : (Object.keys(obj) as Array); + + return keysToCheck.every((key) => obj[key] == null); +} diff --git a/apps/browser/src/background/commands.background.ts b/apps/browser/src/background/commands.background.ts index 2aec43ba4f7..5cf58b1653f 100644 --- a/apps/browser/src/background/commands.background.ts +++ b/apps/browser/src/background/commands.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts index a5d50e8508f..9e10ed974ba 100644 --- a/apps/browser/src/background/idle.background.ts +++ b/apps/browser/src/background/idle.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index fb92ebe04ae..98f3867e5ff 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Subject, filter, firstValueFrom, map, merge, timeout } from "rxjs"; import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common"; @@ -33,7 +35,6 @@ import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/aut import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { KdfConfigService as kdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; @@ -48,7 +49,6 @@ import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; -import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; @@ -76,7 +76,7 @@ import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/bill import { ClientType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; -import { ProcessReloadService } from "@bitwarden/common/key-management/services/process-reload.service"; +import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -204,6 +204,9 @@ import { BiometricStateService, BiometricsService, DefaultBiometricStateService, + DefaultKeyService, + DefaultKdfConfigService, + KdfConfigService, KeyService as KeyServiceAbstraction, } from "@bitwarden/key-management"; import { @@ -230,13 +233,15 @@ import { MainContextMenuHandler } from "../autofill/browser/main-context-menu-ha import LegacyOverlayBackground from "../autofill/deprecated/background/overlay.background.deprecated"; import { Fido2Background as Fido2BackgroundAbstraction } from "../autofill/fido2/background/abstractions/fido2.background"; import { Fido2Background } from "../autofill/fido2/background/fido2.background"; -import { BrowserFido2UserInterfaceService } from "../autofill/fido2/services/browser-fido2-user-interface.service"; +import { + BrowserFido2ParentWindowReference, + BrowserFido2UserInterfaceService, +} from "../autofill/fido2/services/browser-fido2-user-interface.service"; import { AutofillService as AutofillServiceAbstraction } from "../autofill/services/abstractions/autofill.service"; import AutofillService from "../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../autofill/services/inline-menu-field-qualification.service"; import { SafariApp } from "../browser/safariApp"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; -import { BrowserKeyService } from "../key-management/browser-key.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; @@ -335,10 +340,10 @@ export default class MainBackground { policyApiService: PolicyApiServiceAbstraction; sendApiService: SendApiServiceAbstraction; userVerificationApiService: UserVerificationApiServiceAbstraction; - fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; - fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; + fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; + fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; fido2ActiveRequestManager: Fido2ActiveRequestManagerAbstraction; - fido2ClientService: Fido2ClientServiceAbstraction; + fido2ClientService: Fido2ClientServiceAbstraction; avatarService: AvatarServiceAbstraction; mainContextMenuHandler: MainContextMenuHandler; cipherContextMenuHandler: CipherContextMenuHandler; @@ -369,13 +374,14 @@ export default class MainBackground { intraprocessMessagingSubject: Subject>>; userAutoUnlockKeyService: UserAutoUnlockKeyService; scriptInjectorService: BrowserScriptInjectorService; - kdfConfigService: kdfConfigServiceAbstraction; + kdfConfigService: KdfConfigService; offscreenDocumentService: OffscreenDocumentService; syncServiceListener: SyncServiceListener; themeStateService: DefaultThemeStateService; autoSubmitLoginBackground: AutoSubmitLoginBackground; sdkService: SdkService; cipherAuthorizationService: CipherAuthorizationService; + inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; onUpdatedRan: boolean; onReplacedRan: boolean; @@ -410,6 +416,7 @@ export default class MainBackground { await this.refreshMenu(true); if (this.systemService != null) { await this.systemService.clearPendingClipboard(); + await this.biometricsService.setShouldAutopromptNow(false); await this.processReloadService.startProcessReload(this.authService); } }; @@ -625,11 +632,7 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); - this.biometricsService = new BackgroundBrowserBiometricsService( - runtimeNativeMessagingBackground, - ); - - this.kdfConfigService = new KdfConfigService(this.stateProvider); + this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.pinService = new PinService( this.accountService, @@ -643,7 +646,7 @@ export default class MainBackground { this.stateService, ); - this.keyService = new BrowserKeyService( + this.keyService = new DefaultKeyService( this.pinService, this.masterPasswordService, this.keyGenerationService, @@ -654,11 +657,16 @@ export default class MainBackground { this.stateService, this.accountService, this.stateProvider, - this.biometricStateService, - this.biometricsService, this.kdfConfigService, ); + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + this.logService, + this.keyService, + this.biometricStateService, + ); + this.appIdService = new AppIdService(this.storageService, this.logService); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); @@ -689,8 +697,7 @@ export default class MainBackground { this.vaultTimeoutSettingsService, ); - this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); - this.fileUploadService = new FileUploadService(this.logService); + this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.cipherFileUploadService = new CipherFileUploadService( this.apiService, this.fileUploadService, @@ -724,7 +731,7 @@ export default class MainBackground { ); const sdkClientFactory = flagEnabled("sdk") - ? new BrowserSdkClientFactory() + ? new BrowserSdkClientFactory(this.logService) : new NoopSdkClientFactory(); this.sdkService = new DefaultSdkService( sdkClientFactory, @@ -733,7 +740,6 @@ export default class MainBackground { this.accountService, this.kdfConfigService, this.keyService, - this.apiService, ); this.passwordStrengthService = new PasswordStrengthService(); @@ -765,7 +771,10 @@ export default class MainBackground { this.configService, ); - this.devicesService = new DevicesServiceImplementation(this.devicesApiService); + this.devicesService = new DevicesServiceImplementation( + this.devicesApiService, + this.appIdService, + ); this.authRequestService = new AuthRequestService( this.appIdService, @@ -788,6 +797,8 @@ export default class MainBackground { this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( this.stateProvider, + this.platformUtilsService, + this.apiService, ); this.ssoLoginService = new SsoLoginService(this.stateProvider); @@ -804,6 +815,11 @@ export default class MainBackground { this.authService, ); + this.domainSettingsService = new DefaultDomainSettingsService( + this.stateProvider, + this.configService, + ); + this.themeStateService = new DefaultThemeStateService( this.globalStateProvider, this.configService, @@ -843,10 +859,8 @@ export default class MainBackground { this.userVerificationApiService, this.userDecryptionOptionsService, this.pinService, - this.logService, - this.vaultTimeoutSettingsService, - this.platformUtilsService, this.kdfConfigService, + this.biometricsService, ); this.vaultFilterService = new VaultFilterService( @@ -876,6 +890,7 @@ export default class MainBackground { this.stateEventRunnerService, this.taskSchedulerService, this.logService, + this.biometricsService, lockedCallback, logoutCallback, ); @@ -952,6 +967,7 @@ export default class MainBackground { this.totpService = new TotpService(this.cryptoFunctionService, this.logService); this.scriptInjectorService = new BrowserScriptInjectorService( + this.domainSettingsService, this.platformUtilsService, this.logService, ); @@ -1050,14 +1066,6 @@ export default class MainBackground { const systemUtilsServiceReloadCallback = async () => { await this.taskSchedulerService.clearAllScheduledTasks(); - if (this.platformUtilsService.isSafari()) { - // If we do `chrome.runtime.reload` on safari they will send an onInstalled reason of install - // and that prompts us to show a new tab, this apparently doesn't happen on sideloaded - // extensions and only shows itself production scenarios. See: https://bitwarden.atlassian.net/browse/PM-12298 - self.location.reload(); - return; - } - BrowserApi.reloadExtension(); }; @@ -1067,13 +1075,14 @@ export default class MainBackground { this.taskSchedulerService, ); - this.processReloadService = new ProcessReloadService( + this.processReloadService = new DefaultProcessReloadService( this.pinService, this.messagingService, systemUtilsServiceReloadCallback, this.vaultTimeoutSettingsService, this.biometricStateService, this.accountService, + this.logService, ); // Other fields @@ -1107,6 +1116,7 @@ export default class MainBackground { messageListener, this.accountService, lockService, + this.billingAccountProfileStateService, ); this.nativeMessagingBackground = new NativeMessagingBackground( this.keyService, @@ -1226,6 +1236,7 @@ export default class MainBackground { this.i18nService, this.logService, this.billingAccountProfileStateService, + this.accountService, ); this.cipherContextMenuHandler = new CipherContextMenuHandler( @@ -1249,6 +1260,8 @@ export default class MainBackground { this.collectionService, this.organizationService, ); + + this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); } async bootstrap() { @@ -1313,27 +1326,10 @@ export default class MainBackground { await this.initOverlayAndTabsBackground(); - if (flagEnabled("sdk")) { - // Warn if the SDK for some reason can't be initialized - let supported = false; - let error: Error; - try { - supported = await firstValueFrom(this.sdkService.supported$); - } catch (e) { - error = e; - } - - if (!supported) { - this.sdkService - .failedToInitialize("background", error) - .catch((e) => this.logService.error(e)); - } - } - return new Promise((resolve) => { setTimeout(async () => { await this.refreshBadge(); - await this.fullSync(true); + await this.fullSync(false); this.taskSchedulerService.setInterval( ScheduledTaskNames.scheduleNextSyncInterval, 5 * 60 * 1000, // check every 5 minutes @@ -1630,7 +1626,6 @@ export default class MainBackground { this.themeStateService, ); } else { - const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); this.overlayBackground = new OverlayBackground( this.logService, this.cipherService, @@ -1643,7 +1638,7 @@ export default class MainBackground { this.platformUtilsService, this.vaultSettingsService, this.fido2ActiveRequestManager, - inlineMenuFieldQualificationService, + this.inlineMenuFieldQualificationService, this.themeStateService, this.totpService, () => this.generatePassword(), diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 69f66dfa7c8..b08f1c8b566 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -1,8 +1,9 @@ -import { firstValueFrom, map } from "rxjs"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { delay, filter, firstValueFrom, from, map, race, timer } from "rxjs"; 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 { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -12,18 +13,19 @@ 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 { UserKey } from "@bitwarden/common/types/key"; -import { KeyService, BiometricStateService } from "@bitwarden/key-management"; +import { KeyService, BiometricStateService, BiometricsCommands } from "@bitwarden/key-management"; import { BrowserApi } from "../platform/browser/browser-api"; import RuntimeBackground from "./runtime.background"; const MessageValidTimeout = 10 * 1000; +const MessageNoResponseTimeout = 60 * 1000; const HashAlgorithmForEncryption = "sha1"; type Message = { command: string; + messageId?: number; // Filled in by this service userId?: string; @@ -41,6 +43,7 @@ type OuterMessage = { type ReceiveMessage = { timestamp: number; command: string; + messageId: number; response?: any; // Unlock key @@ -51,19 +54,23 @@ type ReceiveMessage = { type ReceiveMessageOuter = { command: string; appId: string; + messageId?: number; // Should only have one of these. message?: EncString; sharedSecret?: string; }; +type Callback = { + resolver: any; + rejecter: any; +}; + export class NativeMessagingBackground { - private connected = false; + connected = false; private connecting: boolean; private port: browser.runtime.Port | chrome.runtime.Port; - private resolver: any = null; - private rejecter: any = null; private privateKey: Uint8Array = null; private publicKey: Uint8Array = null; private secureSetupResolve: any = null; @@ -71,6 +78,11 @@ export class NativeMessagingBackground { private appId: string; private validatingFingerprint: boolean; + private messageId = 0; + private callbacks = new Map(); + + isConnectedToOutdatedDesktopClient = true; + constructor( private keyService: KeyService, private encryptService: EncryptService, @@ -95,6 +107,7 @@ export class NativeMessagingBackground { } async connect() { + this.logService.info("[Native Messaging IPC] Connecting to Bitwarden Desktop app..."); this.appId = await this.appIdService.getAppId(); await this.biometricStateService.setFingerprintValidated(false); @@ -104,6 +117,9 @@ export class NativeMessagingBackground { this.connecting = true; const connectedCallback = () => { + this.logService.info( + "[Native Messaging IPC] Connection to Bitwarden Desktop app established!", + ); this.connected = true; this.connecting = false; resolve(); @@ -121,11 +137,17 @@ export class NativeMessagingBackground { connectedCallback(); break; case "disconnected": + this.logService.info("[Native Messaging IPC] Disconnected from Bitwarden Desktop app."); if (this.connecting) { reject(new Error("startDesktop")); } this.connected = false; this.port.disconnect(); + // reject all + for (const callback of this.callbacks.values()) { + callback.rejecter("disconnected"); + } + this.callbacks.clear(); break; case "setupEncryption": { // Ignore since it belongs to another device @@ -145,6 +167,16 @@ export class NativeMessagingBackground { await this.biometricStateService.setFingerprintValidated(true); } this.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.secureSetupResolve(); break; } @@ -153,17 +185,25 @@ export class NativeMessagingBackground { if (message.appId !== this.appId) { return; } + this.logService.warning( + "[Native Messaging IPC] Secure channel encountered an error; disconnecting and wiping keys...", + ); this.sharedSecret = null; this.privateKey = null; this.connected = false; - this.rejecter({ - message: "invalidateEncryption", - }); + if (this.callbacks.has(message.messageId)) { + this.callbacks.get(message.messageId).rejecter({ + message: "invalidateEncryption", + }); + } return; case "verifyFingerprint": { if (this.sharedSecret == null) { + this.logService.info( + "[Native Messaging IPC] Desktop app requested trust verification by fingerprint.", + ); this.validatingFingerprint = true; // 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 @@ -172,9 +212,11 @@ export class NativeMessagingBackground { break; } case "wrongUserId": - this.rejecter({ - message: "wrongUserId", - }); + if (this.callbacks.has(message.messageId)) { + this.callbacks.get(message.messageId).rejecter({ + message: "wrongUserId", + }); + } return; default: // Ignore since it belongs to another device @@ -208,6 +250,60 @@ 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 }); + }); + message.messageId = messageId; + try { + await this.send(message); + } catch (e) { + this.logService.info( + `[Native Messaging IPC] Error sending message of type ${message.command} to Bitwarden Desktop app. Error: ${e}`, + ); + const callback = this.callbacks.get(messageId); + this.callbacks.delete(messageId); + callback.rejecter("errorConnecting"); + } + + setTimeout(() => { + if (this.callbacks.has(messageId)) { + this.logService.info("[Native Messaging IPC] Message timed out and received no response"); + this.callbacks.get(messageId).rejecter({ + message: "timeout", + }); + this.callbacks.delete(messageId); + } + }, MessageNoResponseTimeout); + + return callback; + } + async send(message: Message) { if (!this.connected) { await this.connect(); @@ -231,20 +327,7 @@ export class NativeMessagingBackground { return await this.encryptService.encrypt(JSON.stringify(message), this.sharedSecret); } - getResponse(): Promise { - return new Promise((resolve, reject) => { - this.resolver = function (response: any) { - resolve(response); - }; - this.rejecter = function (resp: any) { - reject({ - message: resp, - }); - }; - }); - } - - private postMessage(message: OuterMessage) { + private postMessage(message: OuterMessage, messageId?: number) { // Wrap in try-catch to when the port disconnected without triggering `onDisconnect`. try { const msg: any = message; @@ -259,14 +342,20 @@ export class NativeMessagingBackground { }; } this.port.postMessage(msg); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.logService.error("NativeMessaging port disconnected, disconnecting."); + this.logService.info( + "[Native Messaging IPC] Disconnected from Bitwarden Desktop app because of the native port disconnecting.", + ); this.sharedSecret = null; this.privateKey = null; this.connected = false; - this.rejecter("invalidateEncryption"); + if (this.callbacks.has(messageId)) { + this.callbacks.get(messageId).rejecter("invalidateEncryption"); + } } } @@ -283,90 +372,30 @@ export class NativeMessagingBackground { } if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { - this.logService.error("NativeMessage is to old, ignoring."); + this.logService.info("[Native Messaging IPC] Received an old native message, ignoring..."); return; } - switch (message.command) { - case "biometricUnlock": { - if ( - ["not available", "not enabled", "not supported", "not unlocked", "canceled"].includes( - message.response, - ) - ) { - this.rejecter(message.response); - return; - } - - // Check for initial setup of biometric unlock - const enabled = await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$); - if (enabled === null || enabled === false) { - if (message.response === "unlocked") { - await this.biometricStateService.setBiometricUnlockEnabled(true); - } - break; - } + const messageId = message.messageId; - // Ignore unlock if already unlocked - if ((await this.authService.getAuthStatus()) === AuthenticationStatus.Unlocked) { - break; - } - - if (message.response === "unlocked") { - try { - if (message.userKeyB64) { - const userKey = new SymmetricCryptoKey( - Utils.fromB64ToArray(message.userKeyB64), - ) as UserKey; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const isUserKeyValid = await this.keyService.validateUserKey(userKey, activeUserId); - if (isUserKeyValid) { - await this.keyService.setUserKey(userKey, activeUserId); - } else { - this.logService.error("Unable to verify biometric unlocked userkey"); - await this.keyService.clearKeys(activeUserId); - this.rejecter("userkey wrong"); - return; - } - } else { - throw new Error("No key received"); - } - } catch (e) { - this.logService.error("Unable to set key: " + e); - this.rejecter("userkey wrong"); - return; - } - - // Verify key is correct by attempting to decrypt a secret - try { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.keyService.getFingerprint(userId); - } catch (e) { - this.logService.error("Unable to verify key: " + e); - await this.keyService.clearKeys(); - this.rejecter("userkey wrong"); - return; - } - - // 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.runtimeBackground.processMessage({ command: "unlocked" }); - } - break; - } - case "biometricUnlockAvailable": { - this.resolver(message); - break; - } - default: - this.logService.error("NativeMessage, got unknown command: " + message.command); - break; + if ( + message.command == BiometricsCommands.Unlock || + message.command == BiometricsCommands.IsAvailable + ) { + this.logService.info( + `[Native Messaging IPC] Received legacy message of type ${message.command}`, + ); + const messageId = this.callbacks.keys().next().value; + const resolver = this.callbacks.get(messageId); + this.callbacks.delete(messageId); + resolver.resolver(message); + return; } - if (this.resolver) { - this.resolver(message); + if (this.callbacks.has(messageId)) { + this.callbacks.get(messageId).resolver(message); + } else { + this.logService.info("[Native Messaging IPC] Received message without a callback", message); } } @@ -382,6 +411,7 @@ export class NativeMessagingBackground { command: "setupEncryption", publicKey: Utils.fromBufferToB64(publicKey), userId: userId, + messageId: this.messageId++, }); return new Promise((resolve, reject) => (this.secureSetupResolve = resolve)); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index f934c8544bd..38bb2ec50c9 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, mergeMap } from "rxjs"; import { LockService } from "@bitwarden/auth/common"; @@ -5,16 +7,18 @@ import { NotificationsService } from "@bitwarden/common/abstractions/notificatio import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AutofillOverlayVisibility, ExtensionCommand } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { MessageListener, isExternalMessage } from "@bitwarden/common/platform/messaging"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { BiometricsCommands } from "@bitwarden/key-management"; -import { MessageListener, isExternalMessage } from "../../../../libs/common/src/platform/messaging"; import { closeUnlockPopout, openSsoAuthResultPopout, @@ -48,6 +52,7 @@ export default class RuntimeBackground { private messageListener: MessageListener, private accountService: AccountService, private readonly lockService: LockService, + private billingAccountProfileStateService: BillingAccountProfileStateService, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor chrome.runtime.onInstalled.addListener((details: any) => { @@ -67,10 +72,14 @@ export default class RuntimeBackground { sendResponse: (response: any) => void, ) => { const messagesWithResponse = [ - "biometricUnlock", - "biometricUnlockAvailable", + BiometricsCommands.AuthenticateWithBiometrics, + BiometricsCommands.GetBiometricsStatus, + BiometricsCommands.UnlockWithBiometricsForUser, + BiometricsCommands.GetBiometricsStatusForUser, "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag", "getInlineMenuFieldQualificationFeatureFlag", + "getInlineMenuTotpFeatureFlag", + "getUserPremiumStatus", ]; if (messagesWithResponse.includes(msg.command)) { @@ -179,13 +188,17 @@ export default class RuntimeBackground { break; } break; - case "biometricUnlock": { - const result = await this.main.biometricsService.authenticateBiometric(); - return result; + case BiometricsCommands.AuthenticateWithBiometrics: { + return await this.main.biometricsService.authenticateWithBiometrics(); } - case "biometricUnlockAvailable": { - const result = await this.main.biometricsService.isBiometricUnlockAvailable(); - return result; + case BiometricsCommands.GetBiometricsStatus: { + return await this.main.biometricsService.getBiometricsStatus(); + } + case BiometricsCommands.UnlockWithBiometricsForUser: { + return await this.main.biometricsService.unlockWithBiometricsForUser(msg.userId); + } + case BiometricsCommands.GetBiometricsStatusForUser: { + return await this.main.biometricsService.getBiometricsStatusForUser(msg.userId); } case "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag": { return await this.configService.getFeatureFlag( @@ -195,6 +208,18 @@ export default class RuntimeBackground { case "getInlineMenuFieldQualificationFeatureFlag": { return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuFieldQualification); } + case "getUserPremiumStatus": { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const result = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), + ); + return result; + } + case "getInlineMenuTotpFeatureFlag": { + return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuTotp); + } } } @@ -234,9 +259,7 @@ export default class RuntimeBackground { await this.main.refreshBadge(); await this.main.refreshMenu(false); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); break; } case "addToLockedVaultPendingNotifications": @@ -263,9 +286,7 @@ export default class RuntimeBackground { await this.configService.ensureConfigFetched(); await this.main.updateOverlayCiphers(); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); } break; case "openPopup": 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 ef4c39942a2..f658f71a209 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, CurrencyPipe, Location } from "@angular/common"; import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; @@ -5,6 +7,7 @@ import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -19,7 +22,6 @@ import { SectionComponent, } from "@bitwarden/components"; -import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; 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"; @@ -32,7 +34,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ButtonModule, CardComponent, CommonModule, - CurrentAccountComponent, ItemModule, JslibModule, PopupPageComponent, @@ -56,6 +57,7 @@ export class PremiumV2Component extends BasePremiumComponent { dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( i18nService, @@ -66,6 +68,7 @@ export class PremiumV2Component extends BasePremiumComponent { dialogService, environmentService, billingAccountProfileStateService, + accountService, ); // Support old price string. Can be removed in future once all translations are properly updated. diff --git a/apps/browser/src/billing/popup/settings/premium.component.html b/apps/browser/src/billing/popup/settings/premium.component.html deleted file mode 100644 index a8f9855e62d..00000000000 --- a/apps/browser/src/billing/popup/settings/premium.component.html +++ /dev/null @@ -1,72 +0,0 @@ -
-
- -
-

- {{ "premiumMembership" | i18n }} -

-
-
-
-
- -

{{ "premiumNotCurrentMember" | i18n }}

-

{{ "premiumSignUpAndGet" | i18n }}

-
    -
  • - - {{ "ppremiumSignUpStorage" | i18n }} -
  • -
  • - - {{ "premiumSignUpTwoStepOptions" | i18n }} -
  • -
  • - - {{ "ppremiumSignUpReports" | i18n }} -
  • -
  • - - {{ "ppremiumSignUpTotp" | i18n }} -
  • -
  • - - {{ "ppremiumSignUpSupport" | i18n }} -
  • -
  • - - {{ "ppremiumSignUpFuture" | i18n }} -
  • -
-

{{ priceString }}

- - -
- -

{{ "premiumCurrentMember" | i18n }}

-

{{ "premiumCurrentMemberThanks" | i18n }}

- -
-
-
diff --git a/apps/browser/src/billing/popup/settings/premium.component.ts b/apps/browser/src/billing/popup/settings/premium.component.ts deleted file mode 100644 index ed64574d17d..00000000000 --- a/apps/browser/src/billing/popup/settings/premium.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { CurrencyPipe, Location } from "@angular/common"; -import { Component } from "@angular/core"; - -import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; - -@Component({ - selector: "app-premium", - templateUrl: "premium.component.html", -}) -export class PremiumComponent extends BasePremiumComponent { - priceString: string; - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - apiService: ApiService, - configService: ConfigService, - logService: LogService, - private location: Location, - private currencyPipe: CurrencyPipe, - dialogService: DialogService, - environmentService: EnvironmentService, - billingAccountProfileStateService: BillingAccountProfileStateService, - ) { - super( - i18nService, - platformUtilsService, - apiService, - configService, - logService, - dialogService, - environmentService, - billingAccountProfileStateService, - ); - - // Support old price string. Can be removed in future once all translations are properly updated. - const thePrice = this.currencyPipe.transform(this.price, "$"); - // Safari extension crashes due to $1 appearing in the price string ($10.00). Escape the $ to fix. - const formattedPrice = this.platformUtilsService.isSafari() - ? thePrice.replace("$", "$$$") - : thePrice; - this.priceString = i18nService.t("premiumPrice", formattedPrice); - if (this.priceString.indexOf("%price%") > -1) { - this.priceString = this.priceString.replace("%price%", thePrice); - } - } - - goBack() { - this.location.back(); - } -} 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 0cd48c45938..4c4753c3f7f 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 @@ -1,36 +1,168 @@ import { Injectable } from "@angular/core"; -import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { + BiometricsService, + BiometricsCommands, + BiometricsStatus, + KeyService, + BiometricStateService, +} from "@bitwarden/key-management"; -import { BrowserBiometricsService } from "./browser-biometrics.service"; +import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; +import { BrowserApi } from "../../platform/browser/browser-api"; @Injectable() -export class BackgroundBrowserBiometricsService extends BrowserBiometricsService { - constructor(private nativeMessagingBackground: () => NativeMessagingBackground) { +export class BackgroundBrowserBiometricsService extends BiometricsService { + constructor( + private nativeMessagingBackground: () => NativeMessagingBackground, + private logService: LogService, + private keyService: KeyService, + private biometricStateService: BiometricStateService, + ) { super(); } - async authenticateBiometric(): Promise { - const responsePromise = this.nativeMessagingBackground().getResponse(); - await this.nativeMessagingBackground().send({ command: "biometricUnlock" }); - const response = await responsePromise; - return response.response === "unlocked"; + async authenticateWithBiometrics(): Promise { + 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; + } + } catch (e) { + this.logService.info("Biometric authentication failed", e); + return false; + } } - async isBiometricUnlockAvailable(): Promise { - const responsePromise = this.nativeMessagingBackground().getResponse(); - await this.nativeMessagingBackground().send({ command: "biometricUnlockAvailable" }); - const response = await responsePromise; - return response.response === "available"; + async getBiometricsStatus(): Promise { + if (!(await BrowserApi.permissionsGranted(["nativeMessaging"]))) { + return BiometricsStatus.NativeMessagingPermissionMissing; + } + + 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, + }); + + if (response.response) { + return response.response; + } + } + return BiometricsStatus.Available; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return BiometricsStatus.DesktopDisconnected; + } } - async biometricsNeedsSetup(): Promise { - return false; + async unlockWithBiometricsForUser(userId: UserId): Promise { + 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.biometricStateService.setFingerprintValidated(true); + this.keyService.setUserKey(userKey, userId); + return userKey; + } + } else { + return null; + } + } 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.biometricStateService.setFingerprintValidated(true); + this.keyService.setUserKey(userKey, userId); + return userKey; + } + } else { + return null; + } + } + } catch (e) { + this.logService.info("Biometric unlock for user failed", e); + throw new Error("Biometric unlock failed"); + } + + return null; + } + + async getBiometricsStatusForUser(id: UserId): Promise { + try { + await this.ensureConnected(); + + if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { + return await this.getBiometricsStatus(); + } + + return ( + await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.GetBiometricsStatusForUser, + userId: id, + }) + ).response; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return BiometricsStatus.DesktopDisconnected; + } + } + + // the first time we call, this might use an outdated version of the protocol, so we drop the response + private async ensureConnected() { + if (!this.nativeMessagingBackground().connected) { + await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.IsAvailable, + }); + } } - async biometricsSupportsAutoSetup(): Promise { + async getShouldAutopromptNow(): Promise { return false; } - async biometricsSetup(): Promise {} + async setShouldAutopromptNow(value: boolean): Promise {} } diff --git a/apps/browser/src/key-management/biometrics/browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/browser-biometrics.service.ts deleted file mode 100644 index 7ffbed45415..00000000000 --- a/apps/browser/src/key-management/biometrics/browser-biometrics.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { BiometricsService } from "@bitwarden/key-management"; - -import { BrowserApi } from "../../platform/browser/browser-api"; - -@Injectable() -export abstract class BrowserBiometricsService extends BiometricsService { - async supportsBiometric() { - const platformInfo = await BrowserApi.getPlatformInfo(); - if (platformInfo.os === "mac" || platformInfo.os === "win" || platformInfo.os === "linux") { - return true; - } - return false; - } - - abstract authenticateBiometric(): Promise; - abstract isBiometricUnlockAvailable(): Promise; -} diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index f50468c8b7a..d248a630cc6 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -1,34 +1,55 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsCommands, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; + import { BrowserApi } from "../../platform/browser/browser-api"; -import { BrowserBiometricsService } from "./browser-biometrics.service"; +export class ForegroundBrowserBiometricsService extends BiometricsService { + shouldAutopromptNow = true; -export class ForegroundBrowserBiometricsService extends BrowserBiometricsService { - async authenticateBiometric(): Promise { + async authenticateWithBiometrics(): Promise { const response = await BrowserApi.sendMessageWithResponse<{ result: boolean; error: string; - }>("biometricUnlock"); + }>(BiometricsCommands.AuthenticateWithBiometrics); if (!response.result) { throw response.error; } return response.result; } - async isBiometricUnlockAvailable(): Promise { + async getBiometricsStatus(): Promise { const response = await BrowserApi.sendMessageWithResponse<{ - result: boolean; + result: BiometricsStatus; error: string; - }>("biometricUnlockAvailable"); - return response.result && response.result === true; + }>(BiometricsCommands.GetBiometricsStatus); + return response.result; } - async biometricsNeedsSetup(): Promise { - return false; + async unlockWithBiometricsForUser(userId: UserId): Promise { + const response = await BrowserApi.sendMessageWithResponse<{ + result: UserKey; + error: string; + }>(BiometricsCommands.UnlockWithBiometricsForUser, { userId }); + if (!response.result) { + return null; + } + return SymmetricCryptoKey.fromString(response.result.keyB64) as UserKey; } - async biometricsSupportsAutoSetup(): Promise { - return false; + async getBiometricsStatusForUser(id: UserId): Promise { + const response = await BrowserApi.sendMessageWithResponse<{ + result: BiometricsStatus; + error: string; + }>(BiometricsCommands.GetBiometricsStatusForUser, { userId: id }); + return response.result; } - async biometricsSetup(): Promise {} + async getShouldAutopromptNow(): Promise { + return this.shouldAutopromptNow; + } + async setShouldAutopromptNow(value: boolean): Promise { + this.shouldAutopromptNow = value; + } } diff --git a/apps/browser/src/key-management/browser-key.service.ts b/apps/browser/src/key-management/browser-key.service.ts deleted file mode 100644 index 1fa3e111fed..00000000000 --- a/apps/browser/src/key-management/browser-key.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { firstValueFrom } from "rxjs"; - -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { USER_KEY } from "@bitwarden/common/platform/services/key-state/user-key.state"; -import { StateProvider } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; -import { - DefaultKeyService, - BiometricsService, - BiometricStateService, -} from "@bitwarden/key-management"; - -export class BrowserKeyService extends DefaultKeyService { - constructor( - pinService: PinServiceAbstraction, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - keyGenerationService: KeyGenerationService, - cryptoFunctionService: CryptoFunctionService, - encryptService: EncryptService, - platformUtilService: PlatformUtilsService, - logService: LogService, - stateService: StateService, - accountService: AccountService, - stateProvider: StateProvider, - private biometricStateService: BiometricStateService, - private biometricsService: BiometricsService, - kdfConfigService: KdfConfigService, - ) { - super( - pinService, - masterPasswordService, - keyGenerationService, - cryptoFunctionService, - encryptService, - platformUtilService, - logService, - stateService, - accountService, - stateProvider, - kdfConfigService, - ); - } - override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const biometricUnlockPromise = - userId == null - ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) - : this.biometricStateService.getBiometricUnlockEnabled(userId); - return await biometricUnlockPromise; - } - return super.hasUserKeyStored(keySuffix, userId); - } - - /** - * Browser doesn't store biometric keys, so we retrieve them from the desktop and return - * if we successfully saved it into memory as the User Key - * @returns the `UserKey` if the user passes a biometrics prompt, otherwise return `null`. - */ - protected override async getKeyFromStorage( - keySuffix: KeySuffixOptions, - userId?: UserId, - ): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const biometricsResult = await this.biometricsService.authenticateBiometric(); - - if (!biometricsResult) { - return null; - } - - const userKey = await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId)); - if (userKey) { - return userKey; - } - } - - return await super.getKeyFromStorage(keySuffix, userId); - } -} diff --git a/apps/browser/src/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts similarity index 82% rename from apps/browser/src/services/extension-lock-component.service.spec.ts rename to apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index a8a019662ef..92830c35aca 100644 --- a/apps/browser/src/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -2,7 +2,6 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/auth/angular"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, @@ -10,9 +9,15 @@ import { import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; +import { + KeyService, + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; +import { UnlockOptions } from "@bitwarden/key-management/angular"; -import { BrowserRouterService } from "../platform/popup/services/browser-router.service"; +import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; import { ExtensionLockComponentService } from "./extension-lock-component.service"; @@ -26,6 +31,7 @@ describe("ExtensionLockComponentService", () => { let vaultTimeoutSettingsService: MockProxy; let keyService: MockProxy; let routerService: MockProxy; + let biometricStateService: MockProxy; beforeEach(() => { userDecryptionOptionsService = mock(); @@ -35,6 +41,7 @@ describe("ExtensionLockComponentService", () => { vaultTimeoutSettingsService = mock(); keyService = mock(); routerService = mock(); + biometricStateService = mock(); TestBed.configureTestingModule({ providers: [ @@ -67,6 +74,10 @@ describe("ExtensionLockComponentService", () => { provide: BrowserRouterService, useValue: routerService, }, + { + provide: BiometricStateService, + useValue: biometricStateService, + }, ], }); @@ -121,8 +132,7 @@ describe("ExtensionLockComponentService", () => { describe("getAvailableUnlockOptions$", () => { interface MockInputs { hasMasterPassword: boolean; - osSupportsBiometric: boolean; - biometricLockSet: boolean; + biometricsStatusForUser: BiometricsStatus; hasBiometricEncryptedUserKeyStored: boolean; platformSupportsSecureStorage: boolean; pinDecryptionAvailable: boolean; @@ -133,8 +143,7 @@ describe("ExtensionLockComponentService", () => { // MP + PIN + Biometrics available { hasMasterPassword: true, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: true, @@ -148,7 +157,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -156,8 +165,7 @@ describe("ExtensionLockComponentService", () => { // PIN + Biometrics available { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: true, @@ -171,7 +179,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -179,8 +187,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics available: user key stored with no secure storage { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: false, pinDecryptionAvailable: false, @@ -194,7 +201,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -202,8 +209,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics available: no user key stored with no secure storage { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: false, platformSupportsSecureStorage: false, pinDecryptionAvailable: false, @@ -217,7 +223,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -225,8 +231,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics not available: biometric lock not set { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: false, + biometricsStatusForUser: BiometricsStatus.UnlockNeeded, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: false, @@ -240,7 +245,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, + biometricsStatus: BiometricsStatus.UnlockNeeded, }, }, ], @@ -248,8 +253,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics not available: user key not stored { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.NotEnabledInConnectedDesktopApp, hasBiometricEncryptedUserKeyStored: false, platformSupportsSecureStorage: true, pinDecryptionAvailable: false, @@ -263,7 +267,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, + biometricsStatus: BiometricsStatus.NotEnabledInConnectedDesktopApp, }, }, ], @@ -271,8 +275,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics not available: OS doesn't support { hasMasterPassword: false, - osSupportsBiometric: false, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.HardwareUnavailable, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: false, @@ -286,7 +289,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.NotSupportedOnOperatingSystem, + biometricsStatus: BiometricsStatus.HardwareUnavailable, }, }, ], @@ -304,12 +307,17 @@ describe("ExtensionLockComponentService", () => { ); // Biometrics - biometricsService.supportsBiometric.mockResolvedValue(mockInputs.osSupportsBiometric); - vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(mockInputs.biometricLockSet); + biometricsService.getBiometricsStatusForUser.mockResolvedValue( + mockInputs.biometricsStatusForUser, + ); + vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue( + mockInputs.hasBiometricEncryptedUserKeyStored, + ); keyService.hasUserKeyStored.mockResolvedValue(mockInputs.hasBiometricEncryptedUserKeyStored); platformUtilsService.supportsSecureStorage.mockReturnValue( mockInputs.platformSupportsSecureStorage, ); + biometricStateService.biometricUnlockEnabled$ = of(true); // PIN pinService.isPinDecryptionAvailable.mockResolvedValue(mockInputs.pinDecryptionAvailable); diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts new file mode 100644 index 00000000000..711ae4e378f --- /dev/null +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -0,0 +1,88 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { inject } from "@angular/core"; +import { combineLatest, defer, firstValueFrom, map, Observable } from "rxjs"; + +import { + PinServiceAbstraction, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { UserId } from "@bitwarden/common/types/guid"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; + +import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; +import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; + +export class ExtensionLockComponentService implements LockComponentService { + private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); + private readonly biometricsService = inject(BiometricsService); + private readonly pinService = inject(PinServiceAbstraction); + private readonly routerService = inject(BrowserRouterService); + private readonly biometricStateService = inject(BiometricStateService); + + getPreviousUrl(): string | null { + return this.routerService.getPreviousUrl(); + } + + getBiometricsError(error: any): string | null { + const biometricsError = BiometricErrors[error?.message as BiometricErrorTypes]; + + if (!biometricsError) { + return null; + } + + return biometricsError.description; + } + + async isWindowVisible(): Promise { + throw new Error("Method not implemented."); + } + + getBiometricsUnlockBtnText(): string { + return "unlockWithBiometrics"; + } + + getAvailableUnlockOptions$(userId: UserId): Observable { + return combineLatest([ + // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to + defer(async () => { + if (!(await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$))) { + return BiometricsStatus.NotEnabledLocally; + } else { + // TODO remove after 2025.3 + // remove after backward compatibility code for old biometrics ipc protocol is removed + const result: BiometricsStatus = (await Promise.race([ + this.biometricsService.getBiometricsStatusForUser(userId), + new Promise((resolve) => + setTimeout(() => resolve(BiometricsStatus.DesktopDisconnected), 1000), + ), + ])) as BiometricsStatus; + return result; + } + }), + this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), + defer(() => this.pinService.isPinDecryptionAvailable(userId)), + ]).pipe( + map(([biometricsStatus, userDecryptionOptions, pinDecryptionAvailable]) => { + const unlockOpts: UnlockOptions = { + masterPassword: { + enabled: userDecryptionOptions.hasMasterPassword, + }, + pin: { + enabled: pinDecryptionAvailable, + }, + biometrics: { + enabled: biometricsStatus === BiometricsStatus.Available, + biometricsStatus: biometricsStatus, + }, + }; + return unlockOpts; + }), + ); + } +} diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 0d9a4189578..86ea0eebbc8 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.11.0", + "version": "2025.1.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -52,19 +52,37 @@ "permissions": [ "", "*://*/*", - "tabs", + "alarms", + "clipboardRead", + "clipboardWrite", "contextMenus", + "idle", "storage", + "tabs", "unlimitedStorage", + "webNavigation", + "webRequest", + "webRequestBlocking" + ], + "__safari__permissions": [ + "", + "*://*/*", + "alarms", "clipboardRead", "clipboardWrite", + "contextMenus", "idle", - "alarms", + "nativeMessaging", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation", "webRequest", - "webRequestBlocking", - "webNavigation" + "webRequestBlocking" ], "optional_permissions": ["nativeMessaging", "privacy"], + "__firefox__optional_permissions": ["nativeMessaging"], + "__safari__optional_permissions": null, "content_security_policy": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", "sandbox": { "pages": [ @@ -75,6 +93,7 @@ ], "content_security_policy": "sandbox allow-scripts; script-src 'self'" }, + "__firefox__sandbox": null, "commands": { "_execute_browser_action": { "suggested_key": { @@ -83,7 +102,14 @@ }, "description": "__MSG_commandOpenPopup__" }, - "_execute_sidebar_action": { + "__firefox___execute_sidebar_action": { + "suggested_key": { + "default": "Alt+Shift+Y", + "linux": "Alt+Shift+U" + }, + "description": "__MSG_commandOpenSidebar__" + }, + "__opera___execute_sidebar_action": { "suggested_key": { "default": "Alt+Shift+Y", "linux": "Alt+Shift+U" @@ -125,13 +151,20 @@ "overlay/list.html", "popup/fonts/*" ], - "applications": { + "__firefox__browser_specific_settings": { "gecko": { "id": "{446900e4-71c2-419f-a6a7-df9c091e268b}", "strict_min_version": "91.0" } }, - "sidebar_action": { + "__firefox__sidebar_action": { + "default_title": "Bitwarden", + "default_panel": "popup/index.html?uilocation=sidebar", + "default_icon": "images/icon19.png", + "open_at_install": false, + "browser_style": false + }, + "__opera__sidebar_action": { "default_title": "Bitwarden", "default_panel": "popup/index.html?uilocation=sidebar", "default_icon": "images/icon19.png", @@ -140,5 +173,6 @@ }, "storage": { "managed_schema": "managed_schema.json" - } + }, + "__firefox__storage": null } diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index f805b701551..ae7c888eb9b 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.11.0", + "version": "2025.1.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -41,6 +41,9 @@ "background": { "service_worker": "background.js" }, + "__firefox__background": { + "scripts": ["background.js"] + }, "action": { "default_icon": { "19": "images/icon19.png", @@ -51,21 +54,40 @@ }, "permissions": [ "activeTab", - "tabs", + "alarms", + "clipboardRead", + "clipboardWrite", "contextMenus", + "idle", + "offscreen", + "scripting", "storage", + "tabs", "unlimitedStorage", + "webNavigation", + "webRequest", + "webRequestAuthProvider" + ], + "__safari__permissions": [ + "activeTab", + "alarms", "clipboardRead", "clipboardWrite", + "contextMenus", "idle", - "alarms", - "scripting", + "nativeMessaging", "offscreen", + "scripting", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation", "webRequest", - "webRequestAuthProvider", - "webNavigation" + "webRequestAuthProvider" ], "optional_permissions": ["nativeMessaging", "privacy"], + "__firefox__optional_permissions": ["nativeMessaging"], + "__safari__optional_permissions": null, "host_permissions": ["https://*/*", "http://*/*"], "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", @@ -79,6 +101,7 @@ "overlay/list.html" ] }, + "__firefox__sandbox": null, "commands": { "_execute_action": { "suggested_key": { @@ -87,7 +110,7 @@ }, "description": "__MSG_commandOpenPopup__" }, - "_execute_sidebar_action": { + "__firefox___execute_sidebar_action": { "suggested_key": { "default": "Alt+Shift+Y", "linux": "Alt+Shift+U" @@ -133,13 +156,13 @@ "matches": [""] } ], - "applications": { + "__firefox__browser_specific_settings": { "gecko": { "id": "{446900e4-71c2-419f-a6a7-df9c091e268b}", "strict_min_version": "91.0" } }, - "sidebar_action": { + "__firefox__sidebar_action": { "default_title": "Bitwarden", "default_panel": "popup/index.html?uilocation=sidebar", "default_icon": "images/icon19.png", @@ -147,5 +170,6 @@ }, "storage": { "managed_schema": "managed_schema.json" - } + }, + "__firefox__storage": null } diff --git a/apps/browser/src/models/browserComponentState.ts b/apps/browser/src/models/browserComponentState.ts index c5540d088ff..50ee9fe34d4 100644 --- a/apps/browser/src/models/browserComponentState.ts +++ b/apps/browser/src/models/browserComponentState.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; export class BrowserComponentState { diff --git a/apps/browser/src/models/browserGroupingsComponentState.ts b/apps/browser/src/models/browserGroupingsComponentState.ts index 36b4cc9ac04..364f4beb6bf 100644 --- a/apps/browser/src/models/browserGroupingsComponentState.ts +++ b/apps/browser/src/models/browserGroupingsComponentState.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionView } from "@bitwarden/admin-console/common"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify"; diff --git a/apps/browser/src/models/browserSendComponentState.ts b/apps/browser/src/models/browserSendComponentState.ts deleted file mode 100644 index 81dd93323bb..00000000000 --- a/apps/browser/src/models/browserSendComponentState.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify"; - -import { BrowserComponentState } from "./browserComponentState"; - -export class BrowserSendComponentState extends BrowserComponentState { - sends: SendView[]; - - static fromJSON(json: DeepJsonify) { - if (json == null) { - return null; - } - - return Object.assign(new BrowserSendComponentState(), json, { - sends: json.sends?.map((s) => SendView.fromJSON(s)), - }); - } -} diff --git a/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts b/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts index cce41a61ee4..0b0dd21824b 100644 --- a/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts +++ b/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /** * MIT License * diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 072ef74004f..1d8ff65c17d 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { DeviceType } from "@bitwarden/common/enums"; @@ -163,6 +165,21 @@ export class BrowserApi { }); } + /** + * Fetch the currently open browser tab + */ + static async getCurrentTab(): Promise | null { + if (BrowserApi.isManifestVersion(3)) { + return await chrome.tabs.getCurrent(); + } + + return new Promise((resolve) => + chrome.tabs.getCurrent((tab) => { + resolve(tab); + }), + ); + } + static async tabsQuery(options: chrome.tabs.QueryInfo): Promise { return new Promise((resolve) => { chrome.tabs.query(options, (tabs) => { @@ -200,15 +217,17 @@ export class BrowserApi { tab: chrome.tabs.Tab, obj: T, options: chrome.tabs.MessageSendOptions = null, + rejectOnError = false, ): Promise { if (!tab || !tab.id) { return; } - return new Promise((resolve) => { + return new Promise((resolve, reject) => { chrome.tabs.sendMessage(tab.id, obj, options, (response) => { - if (chrome.runtime.lastError) { + if (chrome.runtime.lastError && rejectOnError) { // Some error happened + reject(); } resolve(response); }); @@ -435,6 +454,12 @@ export class BrowserApi { * Handles reloading the extension using the underlying functionality exposed by the browser API. */ static reloadExtension() { + // If we do `chrome.runtime.reload` on safari they will send an onInstalled reason of install + // and that prompts us to show a new tab, this apparently doesn't happen on sideloaded + // extensions and only shows itself production scenarios. See: https://bitwarden.atlassian.net/browse/PM-12298 + if (this.isSafariApi) { + return self.location.reload(); + } return chrome.runtime.reload(); } diff --git a/apps/browser/src/platform/browser/from-chrome-event.ts b/apps/browser/src/platform/browser/from-chrome-event.ts index e45dcdcd082..28e57f58132 100644 --- a/apps/browser/src/platform/browser/from-chrome-event.ts +++ b/apps/browser/src/platform/browser/from-chrome-event.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { BrowserApi } from "./browser-api"; diff --git a/apps/browser/src/platform/flags.ts b/apps/browser/src/platform/flags.ts index 383e982f065..2b1040bcd8a 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -11,13 +11,11 @@ import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-envi import { BrowserApi } from "./browser/browser-api"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { accountSwitching?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { managedEnvironment?: GroupPolicyEnvironment; } & SharedDevFlags; diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index 9256c27f37b..f67b96c847f 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; diff --git a/apps/browser/src/platform/messaging/chrome-message.sender.ts b/apps/browser/src/platform/messaging/chrome-message.sender.ts index 914b8fd43a4..4fc9c22e26b 100644 --- a/apps/browser/src/platform/messaging/chrome-message.sender.ts +++ b/apps/browser/src/platform/messaging/chrome-message.sender.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CommandDefinition, MessageSender } from "@bitwarden/common/platform/messaging"; import { getCommand } from "@bitwarden/common/platform/messaging/internal"; diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.service.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.service.ts index 3a1227ea5e2..db211614c9e 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.service.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { OffscreenDocumentService } from "./abstractions/offscreen-document"; diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts index 4065f2e46d7..67fa920d18d 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -8,6 +8,8 @@ describe("OffscreenDocument", () => { const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); const consoleErrorSpy = jest.spyOn(console, "error"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../offscreen-document/offscreen-document"); describe("init", () => { diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.ts index 938e3191e0d..b1c39d34918 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { BrowserApi } from "../browser/browser-api"; diff --git a/apps/browser/src/platform/popup/browser-popup-utils.ts b/apps/browser/src/platform/popup/browser-popup-utils.ts index fb53d3451f2..33a1ff4016d 100644 --- a/apps/browser/src/platform/popup/browser-popup-utils.ts +++ b/apps/browser/src/platform/popup/browser-popup-utils.ts @@ -1,6 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BrowserApi } from "../browser/browser-api"; import { ScrollOptions } from "./abstractions/browser-popup-utils.abstractions"; +import { PopupWidthOptions } from "./layout/popup-size.service"; class BrowserPopupUtils { /** @@ -21,6 +24,22 @@ class BrowserPopupUtils { return BrowserPopupUtils.urlContainsSearchParams(win, "uilocation", "popout"); } + /** + * Check if the current popup view is open inside of the current browser tab + * (it is possible in Chrome to open the extension in a tab) + */ + static async isInTab() { + const tabId = (await BrowserApi.getCurrentTab())?.id; + + if (tabId === undefined || tabId === null) { + return false; + } + + const result = BrowserApi.getExtensionViews({ tabId, type: "tab" }); + + return result.length > 0; + } + /** * Identifies if the popup is within the single action popout. * @@ -108,7 +127,10 @@ class BrowserPopupUtils { const defaultPopoutWindowOptions: chrome.windows.CreateData = { type: "popup", focused: true, - width: 380, + width: Math.max( + PopupWidthOptions.default, + typeof document === "undefined" ? PopupWidthOptions.default : document.body.clientWidth, + ), height: 630, }; const offsetRight = 15; diff --git a/apps/browser/src/platform/popup/components/pop-out.component.html b/apps/browser/src/platform/popup/components/pop-out.component.html index c3f1f8ca150..3097f6e30d3 100644 --- a/apps/browser/src/platform/popup/components/pop-out.component.html +++ b/apps/browser/src/platform/popup/components/pop-out.component.html @@ -1,9 +1,4 @@ - - - - + -

+

{{ pageTitle }}

diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.ts b/apps/browser/src/platform/popup/layout/popup-header.component.ts index fcf7f57c89f..3a590b284fe 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-header.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BooleanInput, coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; import { Component, Input, Signal, inject } from "@angular/core"; diff --git a/apps/browser/src/platform/popup/layout/popup-layout.mdx b/apps/browser/src/platform/popup/layout/popup-layout.mdx index aa11b4099a9..5723bef44b1 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.mdx +++ b/apps/browser/src/platform/popup/layout/popup-layout.mdx @@ -44,6 +44,9 @@ page looks nice when the extension is popped out. - `above-scroll-area` - When the page content overflows, this content will be "stuck" to the top of the page upon scrolling. +- `full-width-notice` + - Similar to `above-scroll-area`, this content will display before `above-scroll-area` without + container margin or padding. - default - Whatever content you want in `main`. @@ -108,6 +111,30 @@ Common interactive elements to insert into the `end` slot are: - "Add" button: this can be accomplished with the Button component and any custom functionality for that particular page +### Notice + + + + + +Common interactive elements to insert into the `full-width-notice` slot are: + +- `bit-banner`: shows a full-width notice + +Usage example: + +```html + + + + This is an important note about these ciphers + + + + + +``` + ## Popup footer Popup footer should be used when the page displays action buttons. It functions similarly to the diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index 4851541576f..c1ac8823261 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, importProvidersFrom } from "@angular/core"; import { RouterModule } from "@angular/router"; @@ -9,6 +11,7 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service. import { AvatarModule, BadgeModule, + BannerModule, ButtonModule, I18nMockService, IconButtonModule, @@ -38,8 +41,8 @@ class ExtensionContainerComponent {} @Component({ selector: "vault-placeholder", - template: ` - + template: /*html*/ ` + + @@ -117,16 +120,24 @@ class MockCurrentAccountComponent {} @Component({ selector: "mock-search", - template: ` -
- -
- `, + template: ` `, standalone: true, imports: [SearchModule], }) class MockSearchComponent {} +@Component({ + selector: "mock-banner", + template: ` + + This is an important note about these ciphers + + `, + standalone: true, + imports: [BannerModule], +}) +class MockBannerComponent {} + @Component({ selector: "mock-vault-page", template: ` @@ -290,6 +301,12 @@ class MockVaultSubpageComponent {} export default { title: "Browser/Popup Layout", component: PopupPageComponent, + parameters: { + chromatic: { + // Disable tests while we troubleshoot their flaky-ness + disableSnapshot: true, + }, + }, decorators: [ moduleMetadata({ imports: [ @@ -300,6 +317,8 @@ export default { CommonModule, RouterModule, ExtensionContainerComponent, + MockBannerComponent, + MockSearchComponent, MockVaultSubpageComponent, MockVaultPageComponent, MockSendPageComponent, @@ -317,6 +336,10 @@ export default { back: "Back", loading: "Loading", search: "Search", + vault: "Vault", + generator: "Generator", + send: "Send", + settings: "Settings", }); }, }, @@ -410,6 +433,46 @@ export const PopupPageWithFooter: Story = { }), }; +export const CompactMode: Story = { + render: (args) => ({ + props: args, + template: /* HTML */ ` +
+
+

Relaxed

+

+ + + +
+ +
+

Compact

+

+ + + +
+
+ `, + }), + play: async (context) => { + const canvasEl = context.canvasElement; + const updateLabel = (containerId: string) => { + const compact = canvasEl.querySelector( + `#${containerId} [data-testid=popup-layout-scroll-region]`, + ); + const label = canvasEl.querySelector(`#${containerId} .example-label`); + const percentVisible = + 100 - + Math.round((100 * (compact.scrollHeight - compact.clientHeight)) / compact.scrollHeight); + label.textContent = `${percentVisible}% above the fold`; + }; + updateLabel("compact-example"); + updateLabel("regular-example"); + }, +}; + export const PoppedOut: Story = { render: (args) => ({ props: args, @@ -474,3 +537,41 @@ export const TransparentHeader: Story = { `, }), }; + +export const Notice: Story = { + render: (args) => ({ + props: args, + template: /* HTML */ ` + + + + + + + + + `, + }), +}; + +export const WidthOptions: Story = { + render: (args) => ({ + props: args, + template: /* HTML */ ` +
+
Default:
+
+ +
+
Wide:
+
+ +
+
Extra wide:
+
+ +
+
+ `, + }), +}; diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html index 8a7bedf0882..ba9a108a504 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -1,10 +1,11 @@
+
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 7b4665040fb..ca019c16bd7 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-page.component.ts @@ -8,7 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic templateUrl: "popup-page.component.html", standalone: true, host: { - class: "tw-h-full tw-flex tw-flex-col tw-flex-1 tw-overflow-y-hidden", + class: "tw-h-full tw-flex tw-flex-col tw-overflow-y-hidden", }, imports: [CommonModule], }) diff --git a/apps/browser/src/platform/popup/layout/popup-size.service.ts b/apps/browser/src/platform/popup/layout/popup-size.service.ts new file mode 100644 index 00000000000..3ae9a633cab --- /dev/null +++ b/apps/browser/src/platform/popup/layout/popup-size.service.ts @@ -0,0 +1,83 @@ +import { inject, Injectable } from "@angular/core"; +import { map, Observable } from "rxjs"; + +import { + GlobalStateProvider, + KeyDefinition, + POPUP_STYLE_DISK, +} from "@bitwarden/common/platform/state"; + +import BrowserPopupUtils from "../browser-popup-utils"; + +/** + * + * Value represents width in pixels + */ +export const PopupWidthOptions = Object.freeze({ + default: 380, + wide: 480, + "extra-wide": 600, +}); + +type PopupWidthOptions = typeof PopupWidthOptions; +export type PopupWidthOption = keyof PopupWidthOptions; + +const POPUP_WIDTH_KEY_DEF = new KeyDefinition(POPUP_STYLE_DISK, "popup-width", { + deserializer: (s) => s, +}); + +/** + * Handles sizing the popup based on available width/height, which can be affected by + * user default zoom level. + * Updates the extension popup width based on a user setting. + **/ +@Injectable({ providedIn: "root" }) +export class PopupSizeService { + private static readonly LocalStorageKey = "bw-popup-width"; + private readonly state = inject(GlobalStateProvider).get(POPUP_WIDTH_KEY_DEF); + + readonly width$: Observable = this.state.state$.pipe( + map((state) => state ?? "default"), + ); + + async setWidth(width: PopupWidthOption) { + await this.state.update(() => width); + } + + /** Begin listening for state changes */ + async init() { + this.width$.subscribe((width: PopupWidthOption) => { + PopupSizeService.setStyle(width); + localStorage.setItem(PopupSizeService.LocalStorageKey, width); + }); + + const isInChromeTab = await BrowserPopupUtils.isInTab(); + + 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"); + } + } + + private static setStyle(width: PopupWidthOption) { + if (!BrowserPopupUtils.inPopup(window)) { + return; + } + const pxWidth = PopupWidthOptions[width] ?? PopupWidthOptions.default; + + document.body.style.minWidth = `${pxWidth}px`; + } + + /** + * To keep the popup size from flickering on bootstrap, we store the width in `localStorage` so we can quickly & synchronously reference it. + **/ + static initBodyWidthFromLocalStorage() { + const storedValue = localStorage.getItem(PopupSizeService.LocalStorageKey); + this.setStyle(storedValue as any); + } +} 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 a5e08133fe2..a4ae3161b47 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 @@ -7,9 +7,9 @@
  • 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 ced3f6462e9..e01b4efd71b 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 @@ -2,13 +2,14 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LinkModule } from "@bitwarden/components"; @Component({ selector: "popup-tab-navigation", templateUrl: "popup-tab-navigation.component.html", standalone: true, - imports: [CommonModule, LinkModule, RouterModule], + imports: [CommonModule, LinkModule, RouterModule, JslibModule], host: { class: "tw-block tw-h-full tw-w-full tw-flex tw-flex-col", }, @@ -16,25 +17,25 @@ import { LinkModule } from "@bitwarden/components"; export class PopupTabNavigationComponent { navButtons = [ { - label: "Vault", + label: "vault", page: "/tabs/vault", iconKey: "lock", iconKeyActive: "lock-f", }, { - label: "Generator", + label: "generator", page: "/tabs/generator", iconKey: "generate", iconKeyActive: "generate-f", }, { - label: "Send", + label: "send", page: "/tabs/send", iconKey: "send", iconKeyActive: "send-f", }, { - label: "Settings", + label: "settings", page: "/tabs/settings", iconKey: "cog", iconKeyActive: "cog-f", diff --git a/apps/browser/src/platform/popup/services/browser-file-download.service.ts b/apps/browser/src/platform/popup/services/browser-file-download.service.ts index e9aaa639c42..ec04adac2af 100644 --- a/apps/browser/src/platform/popup/services/browser-file-download.service.ts +++ b/apps/browser/src/platform/popup/services/browser-file-download.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { FileDownloadBuilder } from "@bitwarden/common/platform/abstractions/file-download/file-download.builder"; 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 dfc816f4ccc..413bde5fcad 100644 --- a/apps/browser/src/platform/popup/services/browser-router.service.ts +++ b/apps/browser/src/platform/popup/services/browser-router.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { ActivatedRouteSnapshot, NavigationEnd, Router } from "@angular/router"; import { filter } from "rxjs"; 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 8876ac44d59..2dfbf05e3c3 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Location } from "@angular/common"; import { Injectable, inject } from "@angular/core"; import { 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 819a2aa3e46..a717786ae52 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DestroyRef, effect, diff --git a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts index 47a128bc1bc..3d45cf5fd7d 100644 --- a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts +++ b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { filter, mergeMap } from "rxjs"; import { diff --git a/apps/browser/src/platform/services/abstractions/browser-task-scheduler.service.ts b/apps/browser/src/platform/services/abstractions/browser-task-scheduler.service.ts index 58c4eb48897..4801e1e8cd0 100644 --- a/apps/browser/src/platform/services/abstractions/browser-task-scheduler.service.ts +++ b/apps/browser/src/platform/services/abstractions/browser-task-scheduler.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { TaskSchedulerService, ScheduledTaskName } from "@bitwarden/common/platform/scheduling"; diff --git a/apps/browser/src/platform/services/browser-environment.service.ts b/apps/browser/src/platform/services/browser-environment.service.ts index 89f05579c88..278ec6f5cff 100644 --- a/apps/browser/src/platform/services/browser-environment.service.ts +++ b/apps/browser/src/platform/services/browser-environment.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/apps/browser/src/platform/services/browser-local-storage.service.ts b/apps/browser/src/platform/services/browser-local-storage.service.ts index 9c315b7f6f4..30454cf6a77 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import AbstractChromeStorageService, { diff --git a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts index d6ec3dfde96..b177497305b 100644 --- a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts +++ b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts @@ -1,8 +1,22 @@ -import { mock } from "jest-mock-extended"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; +import { + DomainSettingsService, + DefaultDomainSettingsService, +} from "@bitwarden/common/autofill/services/domain-settings.service"; +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"; +import { + FakeStateProvider, + FakeAccountService, + mockAccountServiceWith, +} from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { createChromeTabMock } from "../../autofill/spec/autofill-mocks"; import { BrowserApi } from "../browser/browser-api"; import { @@ -11,8 +25,19 @@ import { } from "./abstractions/script-injector.service"; import { BrowserScriptInjectorService } from "./browser-script-injector.service"; +const mockEquivalentDomains = [ + ["example.com", "exampleapp.com", "example.co.uk", "ejemplo.es"], + ["bitwarden.com", "bitwarden.co.uk", "sm-bitwarden.com"], + ["example.co.uk", "exampleapp.co.uk"], +]; + describe("ScriptInjectorService", () => { const tabId = 1; + const tabMock = createChromeTabMock({ id: tabId }); + const mockBlockedURI = new URL(tabMock.url); + jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); + jest.spyOn(BrowserApi, "isManifestVersion"); + const combinedManifestVersionFile = "content/autofill-init.js"; const mv2SpecificFile = "content/autofill-init-mv2.js"; const mv2Details = { file: mv2SpecificFile }; @@ -22,14 +47,29 @@ describe("ScriptInjectorService", () => { runAt: "document_start", }; const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); + let scriptInjectorService: BrowserScriptInjectorService; - jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); - jest.spyOn(BrowserApi, "isManifestVersion"); - const platformUtilsService = mock(); const logService = mock(); + const platformUtilsService = mock(); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); + let configService: MockProxy; + let domainSettingsService: DomainSettingsService; beforeEach(() => { - scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); + jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(false)); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); + domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); + domainSettingsService.blockedInteractionsUris$ = of({}); + scriptInjectorService = new BrowserScriptInjectorService( + domainSettingsService, + platformUtilsService, + logService, + ); + jest.spyOn(scriptInjectorService as any, "buildInjectionDetails"); }); describe("inject", () => { @@ -71,6 +111,58 @@ describe("ScriptInjectorService", () => { { world: "ISOLATED" }, ); }); + + it("skips injecting the script in manifest v3 when the tab domain is a blocked domain", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ [mockBlockedURI.host]: null }); + manifestVersionSpy.mockReturnValue(3); + + await expect(scriptInjectorService["buildInjectionDetails"]).not.toHaveBeenCalled(); + }); + + it("skips injecting the script in manifest v2 when the tab domain is a blocked domain", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ [mockBlockedURI.host]: null }); + manifestVersionSpy.mockReturnValue(2); + + await expect(scriptInjectorService["buildInjectionDetails"]).not.toHaveBeenCalled(); + }); + + it("injects the script in manifest v2 when given combined injection details", async () => { + manifestVersionSpy.mockReturnValue(2); + + await scriptInjectorService.inject({ + tabId, + injectDetails: { + file: combinedManifestVersionFile, + frame: "all_frames", + ...sharedInjectDetails, + }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabId, { + ...sharedInjectDetails, + allFrames: true, + file: combinedManifestVersionFile, + }); + }); + + it("injects the script in manifest v3 when given combined injection details", async () => { + manifestVersionSpy.mockReturnValue(3); + + await scriptInjectorService.inject({ + tabId, + injectDetails: { + file: combinedManifestVersionFile, + frame: 10, + ...sharedInjectDetails, + }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith( + tabId, + { ...sharedInjectDetails, frameId: 10, file: combinedManifestVersionFile }, + { world: "ISOLATED" }, + ); + }); }); describe("injection of mv2 specific details", () => { diff --git a/apps/browser/src/platform/services/browser-script-injector.service.ts b/apps/browser/src/platform/services/browser-script-injector.service.ts index 5b3a10ef2bb..c2bace669dc 100644 --- a/apps/browser/src/platform/services/browser-script-injector.service.ts +++ b/apps/browser/src/platform/services/browser-script-injector.service.ts @@ -1,3 +1,8 @@ +import { firstValueFrom } from "rxjs"; + +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -10,7 +15,10 @@ import { } from "./abstractions/script-injector.service"; export class BrowserScriptInjectorService extends ScriptInjectorService { + blockedDomains: Set = null; + constructor( + private readonly domainSettingsService: DomainSettingsService, private readonly platformUtilsService: PlatformUtilsService, private readonly logService: LogService, ) { @@ -30,6 +38,28 @@ export class BrowserScriptInjectorService extends ScriptInjectorService { throw new Error("No file specified for script injection"); } + const tab = tabId && (await BrowserApi.getTab(tabId)); + const tabURL = tab?.url ? new URL(tab.url) : null; + + // Check if the tab URI is on the disabled URIs list + let injectionAllowedInTab = true; + const blockedDomains = await firstValueFrom( + this.domainSettingsService.blockedInteractionsUris$, + ); + + if (blockedDomains && tabURL?.hostname) { + const blockedDomainsSet = new Set(Object.keys(blockedDomains)); + + injectionAllowedInTab = !(tabURL && blockedDomainsSet.has(tabURL.hostname)); + } + + if (!injectionAllowedInTab) { + this.logService.warning( + `${injectDetails.file} was not injected because ${tabURL?.hostname || "the tab URI"} is on the user's blocked domains list.`, + ); + return; + } + const injectionDetails = this.buildInjectionDetails(injectDetails, file); if (BrowserApi.isManifestVersion(3)) { diff --git a/apps/browser/src/platform/services/i18n.service.ts b/apps/browser/src/platform/services/i18n.service.ts index a27c3935d75..39ddff87639 100644 --- a/apps/browser/src/platform/services/i18n.service.ts +++ b/apps/browser/src/platform/services/i18n.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service"; import { GlobalStateProvider } from "@bitwarden/common/platform/state"; 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 949ecebde8a..4d6a403a18a 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 @@ -48,6 +48,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.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, @@ -69,6 +71,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.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, 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 e4256d9c0cf..900212ddefa 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Subject } from "rxjs"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -196,6 +198,8 @@ export class LocalBackedSessionStorageService private compareValues(value1: T, value2: T): boolean { try { return compareValues(value1, value2); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.error( `error comparing values\n${JSON.stringify(value1)}\n${JSON.stringify(value2)}`, 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 b47488bdd7d..3679b2731e3 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ExtensionCommand } from "@bitwarden/common/autofill/constants"; import { ClientType, DeviceType } from "@bitwarden/common/enums"; import { 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 35c55633c0c..f6f5e45f093 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,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { switchMap, merge, delay, filter, concatMap, map, first, of } from "rxjs"; import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; diff --git a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts index d3b8f36a540..0499f34a4ae 100644 --- a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts +++ b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts @@ -1,3 +1,6 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import type { BitwardenClient } from "@bitwarden/sdk-internal"; @@ -14,28 +17,36 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } return false; })(); +// Due to using webpack as bundler, sync imports will return an async module. Since we do support +// top level awaits, we define a promise we can await in the `load` function. +let loadingPromise: Promise | undefined; + // Manifest v3 does not support dynamic imports in the service worker. if (BrowserApi.isManifestVersion(3)) { if (supported) { // eslint-disable-next-line no-console console.debug("WebAssembly is supported in this environment"); - import("./wasm"); + loadingPromise = import("./wasm"); } else { // eslint-disable-next-line no-console console.debug("WebAssembly is not supported in this environment"); - import("./fallback"); + loadingPromise = import("./fallback"); } } // Manifest v2 expects dynamic imports to prevent timing issues. async function load() { if (BrowserApi.isManifestVersion(3)) { + // Ensure we have loaded the module + await loadingPromise; return; } @@ -56,11 +67,20 @@ async function load() { * Works both in popup and service worker. */ export class BrowserSdkClientFactory implements SdkClientFactory { + constructor(private logService: LogService) {} + async createSdkClient( ...args: ConstructorParameters ): Promise { + const startTime = performance.now(); await load(); - return Promise.resolve((globalThis as any).init_sdk(...args)); + const endTime = performance.now(); + + const instance = (globalThis as any).init_sdk(...args); + + this.logService.info("WASM SDK loaded in", Math.round(endTime - startTime), "ms"); + + return instance; } } diff --git a/apps/browser/src/platform/services/task-scheduler/background-task-scheduler.service.ts b/apps/browser/src/platform/services/task-scheduler/background-task-scheduler.service.ts index 23b580988f8..b09911480ab 100644 --- a/apps/browser/src/platform/services/task-scheduler/background-task-scheduler.service.ts +++ b/apps/browser/src/platform/services/task-scheduler/background-task-scheduler.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateProvider } from "@bitwarden/common/platform/state"; diff --git a/apps/browser/src/platform/services/task-scheduler/browser-task-scheduler.service.ts b/apps/browser/src/platform/services/task-scheduler/browser-task-scheduler.service.ts index 187742f5891..e0ad7e9bc98 100644 --- a/apps/browser/src/platform/services/task-scheduler/browser-task-scheduler.service.ts +++ b/apps/browser/src/platform/services/task-scheduler/browser-task-scheduler.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, Observable, Subscription } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/browser/src/platform/storage/background-memory-storage.service.ts b/apps/browser/src/platform/storage/background-memory-storage.service.ts index a1d333affa3..9c0cac0d044 100644 --- a/apps/browser/src/platform/storage/background-memory-storage.service.ts +++ b/apps/browser/src/platform/storage/background-memory-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore // eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage specifically for browser backgrounds import { MemoryStorageService } from "@bitwarden/common/platform/state/storage/memory-storage.service"; 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 e1e921cc3a3..f5daff93815 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.spec.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts @@ -3,7 +3,6 @@ import { Subject } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @@ -30,12 +29,12 @@ describe("ForegroundSyncService", () => { const cipherService = mock(); const collectionService = mock(); const apiService = mock(); - const accountService = mock(); + const accountService = mockAccountServiceWith(userId); const authService = mock(); const sendService = mock(); const sendApiService = mock(); const messageListener = mock(); - const stateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); + const stateProvider = new FakeStateProvider(accountService); const sut = new ForegroundSyncService( stateService, diff --git a/apps/browser/src/platform/sync/sync-service.listener.ts b/apps/browser/src/platform/sync/sync-service.listener.ts index 079edbf4c71..b7171528648 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, concatMap, filter } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/browser/src/popup/app-routing.animations.ts b/apps/browser/src/popup/app-routing.animations.ts index 90990ea832d..6af47934ef9 100644 --- a/apps/browser/src/popup/app-routing.animations.ts +++ b/apps/browser/src/popup/app-routing.animations.ts @@ -1,4 +1,19 @@ -import { animate, group, query, style, transition, trigger } from "@angular/animations"; +import { + animate, + AnimationMetadata, + group, + query, + style, + transition, + trigger, +} from "@angular/animations"; + +/** + * Determines the router transition behavior. + * Changing between elevations will animate from the right. + * Navigating between two pages of the same elevation will not animate. + */ +export type RouteElevation = 0 | 1 | 2 | 3 | 4; const queryShown = query( ":enter, :leave", @@ -13,11 +28,15 @@ const queryChildRoute = query("router-outlet ~ *", [style({}), animate(1, style( optional: true, }); -const speed = "0.4s"; +const speedX = "0.225s"; +const speedY = "0.3s"; -export function queryTranslate( - direction: string, - axis: string, +type TranslateDirection = "enter" | "leave"; +type TranslationAxis = "X" | "Y"; + +function queryTranslate( + direction: TranslateDirection, + axis: TranslationAxis, from: number, to: number, zIndex = 1000, @@ -30,214 +49,67 @@ export function queryTranslate( zIndex: zIndex, boxShadow: "0 3px 2px -2px gray", }), - animate(speed + " ease-in-out", style({ transform: "translate" + axis + "(" + to + "%)" })), + animate( + (axis === "X" ? speedX : speedY) + " ease-in-out", + style({ + transform: "translate" + axis + "(" + to + "%)", + }), + ), ], - { optional: true }, - ); -} - -export function queryTranslateX(direction: string, from: number, to: number, zIndex = 1000) { - return queryTranslate(direction, "X", from, to, zIndex); -} - -export function queryTranslateY(direction: string, from: number, to: number, zIndex = 1000) { - return queryTranslate(direction, "Y", from, to, zIndex); -} - -const inSlideLeft = [ - queryShown, - group([queryTranslateX("enter", 100, 0), queryTranslateX("leave", 0, -100), queryChildRoute]), -]; - -const outSlideRight = [ - queryShown, - group([queryTranslateX("enter", -100, 0), queryTranslateX("leave", 0, 100)]), -]; - -const inSlideUp = [ - queryShown, - group([queryTranslateY("enter", 100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]), -]; - -const outSlideDown = [ - queryShown, - group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, 100, 1010)]), -]; - -const inSlideDown = [ - queryShown, - group([queryTranslateY("enter", -100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]), -]; - -// eslint-disable-next-line -const outSlideUp = [ - queryShown, - group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, -100, 1010)]), -]; - -export function tabsToCiphers(fromState: string, toState: string) { - if (fromState == null || toState === null || toState.indexOf("ciphers_") === -1) { - return false; - } - return ( - (fromState.indexOf("ciphers_") === 0 && fromState.indexOf("ciphers_direction=b") === -1) || - fromState === "tabs" - ); -} - -export function ciphersToTabs(fromState: string, toState: string) { - if (fromState == null || toState === null || fromState.indexOf("ciphers_") === -1) { - return false; - } - return toState.indexOf("ciphers_direction=b") === 0 || toState === "tabs"; -} - -export function ciphersToView(fromState: string, toState: string) { - if (fromState == null || toState === null) { - return false; - } - return ( - fromState.indexOf("ciphers_") === 0 && - (toState === "view-cipher" || toState === "add-cipher" || toState === "clone-cipher") + { + optional: true, + }, ); } -export function viewToCiphers(fromState: string, toState: string) { - if (fromState == null || toState === null) { - return false; - } - return ( - (fromState === "view-cipher" || fromState === "add-cipher" || fromState === "clone-cipher") && - toState.indexOf("ciphers_") === 0 - ); -} +const animations = { + slideInFromRight: [ + queryShown, + group([ + queryTranslate("enter", "X", 100, 0, 1010), + queryTranslate("leave", "X", 0, 0), + queryChildRoute, + ]), + ], + slideOutToRight: [ + queryShown, + group([queryTranslate("enter", "X", 0, 0), queryTranslate("leave", "X", 0, 100, 1010)]), + ], + /** --- Not used --- */ + // slideInFromTop: [ + // queryShown, + // group([ + // queryTranslate("enter", "Y", -100, 0, 1010), + // queryTranslate("leave", "Y", 0, 0), + // queryChildRoute, + // ]), + // ], + // slideOutToTop: [ + // queryShown, + // group([queryTranslate("enter", "Y", 0, 0), queryTranslate("leave", "Y", 0, -100, 1010)]), + // ], +} satisfies Record; export const routerTransition = trigger("routerTransition", [ - transition("void => home", inSlideLeft), - transition("void => tabs", inSlideLeft), - - transition("home => environment, home => login, home => register", inSlideUp), - - transition("login => home", outSlideDown), - transition("login => hint", inSlideUp), - transition("login => tabs, login => 2fa, login => login-with-device", inSlideLeft), - - transition("hint => login, register => home, environment => home", outSlideDown), - - transition("2fa => login", outSlideRight), - transition("2fa => 2fa-options", inSlideUp), - transition("2fa-options => 2fa", outSlideDown), - transition("2fa => tabs", inSlideLeft), - - transition("login-with-device => tabs, login-with-device => 2fa", inSlideLeft), - transition("login-with-device => login", outSlideRight), - - transition(tabsToCiphers, inSlideLeft), - transition(ciphersToTabs, outSlideRight), - - transition(ciphersToView, inSlideUp), - transition(viewToCiphers, outSlideDown), - - transition("tabs => view-cipher", inSlideUp), - transition("view-cipher => tabs", outSlideDown), - - transition("view-cipher => edit-cipher, view-cipher => cipher-password-history", inSlideUp), - transition( - "edit-cipher => view-cipher, cipher-password-history => view-cipher, edit-cipher => tabs", - outSlideDown, - ), - - transition("view-cipher => clone-cipher", inSlideUp), - transition("clone-cipher => view-cipher, clone-cipher => tabs", outSlideDown), - - transition("view-cipher => share-cipher", inSlideUp), - transition("share-cipher => view-cipher", outSlideDown), - - transition("tabs => add-cipher", inSlideUp), - transition("add-cipher => tabs", outSlideDown), - - transition("generator => generator-history, tabs => generator-history", inSlideLeft), - transition("generator-history => generator, generator-history => tabs", outSlideRight), - - transition( - "add-cipher => generator, edit-cipher => generator, clone-cipher => generator", - inSlideUp, - ), - transition( - "generator => add-cipher, generator => edit-cipher, generator => clone-cipher", - outSlideDown, - ), - - transition("edit-cipher => attachments, edit-cipher => collections", inSlideLeft), - transition("attachments => edit-cipher, collections => edit-cipher", outSlideRight), - - transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft), - transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight), - - transition("tabs => account-security", inSlideLeft), - transition("account-security => tabs", outSlideRight), - - transition("tabs => assign-collections", inSlideLeft), - transition("assign-collections => tabs", outSlideRight), - - // Vault settings - transition("tabs => vault-settings", inSlideLeft), - transition("vault-settings => tabs", outSlideRight), - - transition("vault-settings => import", inSlideLeft), - transition("import => vault-settings", outSlideRight), - - transition("vault-settings => export", inSlideLeft), - transition("export => vault-settings", outSlideRight), - - transition("vault-settings => folders", inSlideLeft), - transition("folders => vault-settings", outSlideRight), - - transition("folders => edit-folder, folders => add-folder", inSlideUp), - transition("edit-folder => folders, add-folder => folders", outSlideDown), - - transition("vault-settings => sync", inSlideLeft), - transition("sync => vault-settings", outSlideRight), - - transition("vault-settings => trash", inSlideLeft), - transition("trash => vault-settings", outSlideRight), - - transition("trash => view-cipher", inSlideLeft), - transition("view-cipher => trash", outSlideRight), - - // Appearance settings - transition("tabs => appearance", inSlideLeft), - transition("appearance => tabs", outSlideRight), - - transition("tabs => premium", inSlideLeft), - transition("premium => tabs", outSlideRight), - - transition("tabs => lock", inSlideDown), - - transition("tabs => about", inSlideLeft), - transition("about => tabs", outSlideRight), - - transition("tabs => send-type", inSlideLeft), - transition("send-type => tabs", outSlideRight), - - transition("tabs => add-send, send-type => add-send", inSlideUp), - transition("add-send => tabs, add-send => send-type", outSlideDown), - - transition("tabs => edit-send, send-type => edit-send", inSlideUp), - transition("edit-send => tabs, edit-send => send-type", outSlideDown), - - // Notification settings - transition("tabs => notifications", inSlideLeft), - transition("notifications => tabs", outSlideRight), - - transition("notifications => excluded-domains", inSlideLeft), - transition("excluded-domains => notifications", outSlideRight), - - transition("tabs => autofill", inSlideLeft), - transition("autofill => tabs", outSlideRight), - - transition("* => account-switcher", inSlideUp), - transition("account-switcher => *", outSlideDown), - - transition("lock => *", outSlideDown), + transition("0 => 1", animations.slideInFromRight), + transition("0 => 2", animations.slideInFromRight), + transition("0 => 3", animations.slideInFromRight), + transition("0 => 4", animations.slideInFromRight), + transition("1 => 2", animations.slideInFromRight), + transition("1 => 3", animations.slideInFromRight), + transition("1 => 4", animations.slideInFromRight), + transition("2 => 3", animations.slideInFromRight), + transition("2 => 4", animations.slideInFromRight), + transition("3 => 4", animations.slideInFromRight), + + transition("1 => 0", animations.slideOutToRight), + transition("2 => 0", animations.slideOutToRight), + transition("2 => 1", animations.slideOutToRight), + transition("3 => 0", animations.slideOutToRight), + transition("3 => 1", animations.slideOutToRight), + transition("3 => 2", animations.slideOutToRight), + transition("4 => 0", animations.slideOutToRight), + transition("4 => 1", animations.slideOutToRight), + transition("4 => 2", animations.slideOutToRight), + transition("4 => 3", animations.slideOutToRight), ]); diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index d53e51e9df2..d55bebfa0c3 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -6,6 +6,7 @@ import { EnvironmentSelectorRouteData, ExtensionDefaultOverlayPosition, } from "@bitwarden/angular/auth/components/environment-selector.component"; +import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component"; import { unauthUiRefreshRedirect } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-redirect"; import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap"; import { @@ -16,15 +17,15 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { extensionRefreshRedirect } from "@bitwarden/angular/utils/extension-refresh-redirect"; -import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; +import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap"; +import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LoginComponent, LoginSecondaryContentComponent, LockIcon, - LockV2Component, + LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, RegistrationLockAltIcon, @@ -35,10 +36,19 @@ import { SetPasswordJitComponent, UserLockIcon, VaultIcon, + LoginDecryptionOptionsComponent, + DevicesIcon, + SsoComponent, + TwoFactorTimeoutIcon, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { LockComponent } from "@bitwarden/key-management/angular"; +import { + NewDeviceVerificationNoticePageOneComponent, + NewDeviceVerificationNoticePageTwoComponent, + VaultIcons, +} from "@bitwarden/vault"; -import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { EnvironmentComponent } from "../auth/popup/environment.component"; @@ -48,93 +58,60 @@ import { } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; import { HintComponent } from "../auth/popup/hint.component"; import { HomeComponent } from "../auth/popup/home.component"; -import { LockComponent } from "../auth/popup/lock.component"; -import { LoginDecryptionOptionsComponent } from "../auth/popup/login-decryption-options/login-decryption-options.component"; +import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/popup/login-v1.component"; -import { LoginViaAuthRequestComponent } from "../auth/popup/login-via-auth-request.component"; +import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; import { RegisterComponent } from "../auth/popup/register.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; -import { AccountSecurityComponent as AccountSecurityV1Component } from "../auth/popup/settings/account-security-v1.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; -import { SsoComponent } from "../auth/popup/sso.component"; +import { SsoComponentV1 } from "../auth/popup/sso-v1.component"; import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; -import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; -import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; -import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; +import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-domains.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; -import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; -import { PremiumComponent } from "../billing/popup/settings/premium.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"; import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component"; -import { GeneratorComponent } from "../tools/popup/generator/generator.component"; -import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; -import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; -import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; -import { SendTypeComponent } from "../tools/popup/send/send-type.component"; import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/send-v2/add-edit/send-add-edit.component"; 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 { AboutPageComponent } from "../tools/popup/settings/about-page/about-page.component"; import { MoreFromBitwardenPageV2Component } from "../tools/popup/settings/about-page/more-from-bitwarden-page-v2.component"; -import { MoreFromBitwardenPageComponent } from "../tools/popup/settings/about-page/more-from-bitwarden-page.component"; import { ExportBrowserV2Component } from "../tools/popup/settings/export/export-browser-v2.component"; -import { ExportBrowserComponent } from "../tools/popup/settings/export/export-browser.component"; import { ImportBrowserV2Component } from "../tools/popup/settings/import/import-browser-v2.component"; -import { ImportBrowserComponent } from "../tools/popup/settings/import/import-browser.component"; import { SettingsV2Component } from "../tools/popup/settings/settings-v2.component"; -import { SettingsComponent } from "../tools/popup/settings/settings.component"; import { clearVaultStateGuard } from "../vault/guards/clear-vault-state.guard"; -import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; -import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; -import { CollectionsComponent } from "../vault/popup/components/vault/collections.component"; -import { CurrentTabComponent } from "../vault/popup/components/vault/current-tab.component"; -import { PasswordHistoryComponent } from "../vault/popup/components/vault/password-history.component"; -import { ShareComponent } from "../vault/popup/components/vault/share.component"; -import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component"; -import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component"; -import { VaultV2Component } from "../vault/popup/components/vault/vault-v2.component"; -import { ViewComponent } from "../vault/popup/components/vault/view.component"; import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/add-edit-v2.component"; import { AssignCollections } from "../vault/popup/components/vault-v2/assign-collections/assign-collections.component"; import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component"; import { PasswordHistoryV2Component } from "../vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component"; +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 { AppearanceComponent } from "../vault/popup/settings/appearance.component"; -import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; -import { FoldersComponent } from "../vault/popup/settings/folders.component"; -import { SyncComponent } from "../vault/popup/settings/sync.component"; import { TrashComponent } from "../vault/popup/settings/trash.component"; import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component"; -import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component"; +import { RouteElevation } from "./app-routing.animations"; import { debounceNavigationGuard } from "./services/debounce-navigation.service"; import { TabsV2Component } from "./tabs-v2.component"; -import { TabsComponent } from "./tabs.component"; /** * Data properties acceptable for use in extension route objects */ export interface RouteDataProperties { + elevation: RouteElevation; + /** - * A state string which identifies the current route for the sake of transition animation logic. - * The state string is passed into [@routerTransition] in the app.component. - */ - state: string; - /** - * A boolean to indicate that the URL should not be saved in memory in the BrowserRouterSvc. + * A boolean to indicate that the URL should not be saved in memory in the BrowserRouterService. */ doNotSaveUrl?: boolean; } @@ -164,31 +141,13 @@ const routes: Routes = [ path: "home", component: HomeComponent, canActivate: [unauthGuardFn(unauthRouteOverrides), unauthUiRefreshRedirect("/login")], - data: { state: "home" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(Fido2V1Component, Fido2Component, { + { path: "fido2", + component: Fido2Component, canActivate: [fido2AuthGuard], - data: { state: "fido2" } satisfies RouteDataProperties, - }), - { - path: "login-with-device", - component: LoginViaAuthRequestComponent, - canActivate: [], - data: { state: "login-with-device" } satisfies RouteDataProperties, - }, - { - path: "admin-approval-requested", - component: LoginViaAuthRequestComponent, - canActivate: [], - data: { state: "login-with-device" } satisfies RouteDataProperties, - }, - { - path: "lock", - component: LockComponent, - canActivate: [lockGuard()], - canMatch: [extensionRefreshRedirect("/lockV2")], - data: { state: "lock", doNotSaveUrl: true } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, ...twofactorRefactorSwap( TwoFactorComponent, @@ -196,12 +155,12 @@ const routes: Routes = [ { path: "2fa", canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "2fa" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { path: "2fa", canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "2fa" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, children: [ { path: "", @@ -211,204 +170,292 @@ const routes: Routes = [ }, ), { - path: "2fa-options", - component: TwoFactorOptionsComponent, - canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "2fa-options" } satisfies RouteDataProperties, - }, - { - path: "login-initiated", - component: LoginDecryptionOptionsComponent, - canActivate: [tdeDecryptionRequiredGuard()], - data: { state: "login-initiated" } satisfies RouteDataProperties, + path: "", + component: ExtensionAnonLayoutWrapperComponent, + children: [ + { + path: "2fa-timeout", + canActivate: [unauthGuardFn(unauthRouteOverrides)], + children: [ + { + path: "", + component: TwoFactorTimeoutComponent, + }, + ], + data: { + pageTitle: { + key: "authenticationTimeout", + }, + pageIcon: TwoFactorTimeoutIcon, + elevation: 1, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, + ], }, { - path: "sso", - component: SsoComponent, + path: "2fa-options", + component: TwoFactorOptionsComponent, canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "sso" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, + ...unauthUiRefreshSwap( + SsoComponentV1, + ExtensionAnonLayoutWrapperComponent, + { + path: "sso", + canActivate: [unauthGuardFn(unauthRouteOverrides)], + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { + path: "sso", + canActivate: [unauthGuardFn(unauthRouteOverrides)], + data: { + pageIcon: VaultIcon, + pageTitle: { + key: "enterpriseSingleSignOn", + }, + pageSubtitle: { + key: "singleSignOnEnterOrgIdentifierText", + }, + elevation: 1, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, + children: [ + { path: "", component: SsoComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + data: { + overlayPosition: ExtensionDefaultOverlayPosition, + } satisfies EnvironmentSelectorRouteData, + }, + ], + }, + ), { path: "set-password", component: SetPasswordComponent, - data: { state: "set-password" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { path: "remove-password", component: RemovePasswordComponent, canActivate: [authGuard], - data: { state: "remove-password" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { path: "register", component: RegisterComponent, canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "register" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { path: "environment", component: EnvironmentComponent, canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "environment" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { - path: "ciphers", - component: VaultItemsComponent, - canActivate: [authGuard], - data: { state: "ciphers" } satisfies RouteDataProperties, - }, - ...extensionRefreshSwap(ViewComponent, ViewV2Component, { path: "view-cipher", + component: ViewV2Component, canActivate: [authGuard], - data: { state: "view-cipher" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(PasswordHistoryComponent, PasswordHistoryV2Component, { + data: { + // Above "trash" + elevation: 3, + } satisfies RouteDataProperties, + }, + { path: "cipher-password-history", + component: PasswordHistoryV2Component, canActivate: [authGuard], - data: { state: "cipher-password-history" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "add-cipher", + component: AddEditV2Component, canActivate: [authGuard, debounceNavigationGuard()], - data: { state: "add-cipher" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, runGuardsAndResolvers: "always", - }), - ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { + }, + { path: "edit-cipher", + component: AddEditV2Component, canActivate: [authGuard, debounceNavigationGuard()], - data: { state: "edit-cipher" } satisfies RouteDataProperties, + data: { + // Above "trash" + elevation: 3, + } satisfies RouteDataProperties, runGuardsAndResolvers: "always", - }), - { - path: "share-cipher", - component: ShareComponent, - canActivate: [authGuard], - data: { state: "share-cipher" } satisfies RouteDataProperties, }, { - path: "collections", - component: CollectionsComponent, - canActivate: [authGuard], - data: { state: "collections" } satisfies RouteDataProperties, - }, - ...extensionRefreshSwap(AttachmentsComponent, AttachmentsV2Component, { path: "attachments", + component: AttachmentsV2Component, canActivate: [authGuard], - data: { state: "attachments" } satisfies RouteDataProperties, - }), + data: { elevation: 1 } satisfies RouteDataProperties, + }, { path: "generator", - component: GeneratorComponent, + component: CredentialGeneratorComponent, canActivate: [authGuard], - data: { state: "generator" } satisfies RouteDataProperties, + data: { elevation: 0 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(PasswordGeneratorHistoryComponent, CredentialGeneratorHistoryComponent, { + { path: "generator-history", + component: CredentialGeneratorHistoryComponent, canActivate: [authGuard], - data: { state: "generator-history" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(ImportBrowserComponent, ImportBrowserV2Component, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "import", + component: ImportBrowserV2Component, canActivate: [authGuard], - data: { state: "import" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(ExportBrowserComponent, ExportBrowserV2Component, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "export", + component: ExportBrowserV2Component, canActivate: [authGuard], - data: { state: "export" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AutofillV1Component, AutofillComponent, { + data: { elevation: 2 } satisfies RouteDataProperties, + }, + { path: "autofill", + component: AutofillComponent, canActivate: [authGuard], - data: { state: "autofill" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AccountSecurityV1Component, AccountSecurityComponent, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "account-security", + component: AccountSecurityComponent, canActivate: [authGuard], - data: { state: "account-security" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "notifications", + component: NotificationsSettingsComponent, canActivate: [authGuard], - data: { state: "notifications" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(VaultSettingsComponent, VaultSettingsV2Component, { - path: "vault-settings", - canActivate: [authGuard], - data: { state: "vault-settings" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(FoldersComponent, FoldersV2Component, { - path: "folders", - canActivate: [authGuard], - data: { state: "folders" } satisfies RouteDataProperties, - }), + data: { elevation: 1 } satisfies RouteDataProperties, + }, { - path: "add-folder", - component: FolderAddEditComponent, + path: "vault-settings", + component: VaultSettingsV2Component, canActivate: [authGuard], - data: { state: "add-folder" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { - path: "edit-folder", - component: FolderAddEditComponent, + path: "folders", + component: FoldersV2Component, canActivate: [authGuard], - data: { state: "edit-folder" } satisfies RouteDataProperties, + data: { elevation: 2 } satisfies RouteDataProperties, }, { - path: "sync", - component: SyncComponent, + path: "blocked-domains", + component: BlockedDomainsComponent, canActivate: [authGuard], - data: { state: "sync" } satisfies RouteDataProperties, + data: { elevation: 2 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { + { path: "excluded-domains", + component: ExcludedDomainsComponent, canActivate: [authGuard], - data: { state: "excluded-domains" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(PremiumComponent, PremiumV2Component, { + data: { elevation: 2 } satisfies RouteDataProperties, + }, + { path: "premium", - component: PremiumComponent, + component: PremiumV2Component, canActivate: [authGuard], - data: { state: "premium" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AppearanceComponent, AppearanceV2Component, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "appearance", + component: AppearanceV2Component, canActivate: [authGuard], - data: { state: "appearance" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { - path: "clone-cipher", - canActivate: [authGuard], - data: { state: "clone-cipher" } satisfies RouteDataProperties, - }), + data: { elevation: 1 } satisfies RouteDataProperties, + }, { - path: "send-type", - component: SendTypeComponent, + path: "clone-cipher", + component: AddEditV2Component, canActivate: [authGuard], - data: { state: "send-type" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(SendAddEditComponent, SendAddEditV2Component, { + { path: "add-send", + component: SendAddEditV2Component, canActivate: [authGuard], - data: { state: "add-send" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(SendAddEditComponent, SendAddEditV2Component, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "edit-send", + component: SendAddEditV2Component, canActivate: [authGuard], - data: { state: "edit-send" } satisfies RouteDataProperties, - }), + data: { elevation: 1 } satisfies RouteDataProperties, + }, { path: "send-created", component: SendCreatedComponent, canActivate: [authGuard], - data: { state: "send" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, { path: "update-temp-password", component: UpdateTempPasswordComponent, canActivate: [authGuard], - data: { state: "update-temp-password" } satisfies RouteDataProperties, + data: { elevation: 1 } satisfies RouteDataProperties, }, + ...unauthUiRefreshSwap( + LoginViaAuthRequestComponentV1, + ExtensionAnonLayoutWrapperComponent, + { + path: "login-with-device", + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { + path: "login-with-device", + data: { + pageIcon: DevicesIcon, + pageTitle: { + key: "loginInitiated", + }, + pageSubtitle: { + key: "aNotificationWasSentToYourDevice", + }, + showLogo: false, + showBackButton: true, + elevation: 1, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, + children: [ + { path: "", component: LoginViaAuthRequestComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + ), + ...unauthUiRefreshSwap( + LoginViaAuthRequestComponentV1, + ExtensionAnonLayoutWrapperComponent, + { + path: "admin-approval-requested", + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { + path: "admin-approval-requested", + data: { + pageIcon: DevicesIcon, + pageTitle: { + key: "adminApprovalRequested", + }, + pageSubtitle: { + key: "adminApprovalRequestSentToAdmins", + }, + showLogo: false, + showBackButton: true, + elevation: 1, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, + children: [{ path: "", component: LoginViaAuthRequestComponent }], + }, + ), ...unauthUiRefreshSwap( HintComponent, ExtensionAnonLayoutWrapperComponent, @@ -416,7 +463,7 @@ const routes: Routes = [ path: "hint", canActivate: [unauthGuardFn(unauthRouteOverrides)], data: { - state: "hint", + elevation: 1, } satisfies RouteDataProperties, }, { @@ -434,7 +481,7 @@ const routes: Routes = [ }, pageIcon: UserLockIcon, showBackButton: true, - state: "hint", + elevation: 1, } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, children: [ { path: "", component: PasswordHintComponent }, @@ -457,7 +504,7 @@ const routes: Routes = [ { path: "login", canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { state: "login" }, + data: { elevation: 1 }, }, { path: "", @@ -470,7 +517,7 @@ const routes: Routes = [ pageTitle: { key: "logInToBitwarden", }, - state: "login", + elevation: 1, showAcctSwitcher: true, } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, children: [ @@ -489,6 +536,23 @@ const routes: Routes = [ ], }, ), + ...unauthUiRefreshSwap( + LoginDecryptionOptionsComponentV1, + ExtensionAnonLayoutWrapperComponent, + { + path: "login-initiated", + canActivate: [tdeDecryptionRequiredGuard()], + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { + path: "login-initiated", + canActivate: [tdeDecryptionRequiredGuard()], + data: { + pageIcon: DevicesIcon, + }, + children: [{ path: "", component: LoginDecryptionOptionsComponent }], + }, + ), { path: "", component: ExtensionAnonLayoutWrapperComponent, @@ -497,7 +561,7 @@ const routes: Routes = [ path: "signup", canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { - state: "signup", + elevation: 1, pageIcon: RegistrationUserAddIcon, pageTitle: { key: "createAccount", @@ -524,7 +588,7 @@ const routes: Routes = [ canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { pageIcon: RegistrationLockAltIcon, - state: "finish-signup", + elevation: 1, showBackButton: true, } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, children: [ @@ -535,8 +599,8 @@ const routes: Routes = [ ], }, { - path: "lockV2", - canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()], + path: "lock", + canActivate: [lockGuard()], data: { pageIcon: LockIcon, pageTitle: { @@ -544,11 +608,22 @@ const routes: Routes = [ }, showReadonlyHostname: true, showAcctSwitcher: true, - } satisfies ExtensionAnonLayoutWrapperData, + elevation: 1, + /** + * This ensures that in a passkey flow the `/fido2?` URL does not get + * overwritten in the `BrowserRouterService` by the `/lock` route. This way, after + * unlocking, the user can be redirected back to the `/fido2?` URL. + * + * Also, this prevents a routing loop when using biometrics to unlock the vault in MV2 (Firefox), + * locking up the browser (https://bitwarden.atlassian.net/browse/PM-16116). This involves the + * `popup-router-cache.service` pushing the `lock` route to the history. + */ + doNotSaveUrl: true, + } satisfies ExtensionAnonLayoutWrapperData & RouteDataProperties, children: [ { path: "", - component: LockV2Component, + component: LockComponent, }, ], }, @@ -569,7 +644,7 @@ const routes: Routes = [ pageSubtitle: { key: "finishJoiningThisOrganizationBySettingAMasterPassword", }, - state: "set-password-jit", + elevation: 1, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, ], @@ -577,22 +652,53 @@ const routes: Routes = [ { path: "assign-collections", component: AssignCollections, - canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh, true, "/")], - data: { state: "assign-collections" } satisfies RouteDataProperties, + canActivate: [authGuard], + data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(AboutPageComponent, AboutPageV2Component, { + { path: "about", + component: AboutPageV2Component, canActivate: [authGuard], - data: { state: "about" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(MoreFromBitwardenPageComponent, MoreFromBitwardenPageV2Component, { + data: { elevation: 1 } satisfies RouteDataProperties, + }, + { path: "more-from-bitwarden", + component: MoreFromBitwardenPageV2Component, canActivate: [authGuard], - data: { state: "moreFromBitwarden" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(TabsComponent, TabsV2Component, { + data: { elevation: 2 } satisfies RouteDataProperties, + }, + { + 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", - data: { state: "tabs" } satisfies RouteDataProperties, + component: TabsV2Component, + data: { elevation: 0 } satisfies RouteDataProperties, children: [ { path: "", @@ -601,45 +707,45 @@ const routes: Routes = [ }, { path: "current", - component: CurrentTabComponent, - canActivate: [authGuard], - canMatch: [extensionRefreshRedirect("/tabs/vault")], - data: { state: "tabs_current" } satisfies RouteDataProperties, - runGuardsAndResolvers: "always", + redirectTo: "/tabs/vault", }, - ...extensionRefreshSwap(VaultFilterComponent, VaultV2Component, { + { path: "vault", - canActivate: [authGuard], + component: VaultV2Component, + canActivate: [authGuard, NewDeviceVerificationNoticeGuard], canDeactivate: [clearVaultStateGuard], - data: { state: "tabs_vault" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(GeneratorComponent, CredentialGeneratorComponent, { + data: { elevation: 0 } satisfies RouteDataProperties, + }, + { path: "generator", + component: CredentialGeneratorComponent, canActivate: [authGuard], - data: { state: "tabs_generator" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(SettingsComponent, SettingsV2Component, { + data: { elevation: 0 } satisfies RouteDataProperties, + }, + { path: "settings", + component: SettingsV2Component, canActivate: [authGuard], - data: { state: "tabs_settings" } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(SendGroupingsComponent, SendV2Component, { + data: { elevation: 0 } satisfies RouteDataProperties, + }, + { path: "send", + component: SendV2Component, canActivate: [authGuard], - data: { state: "tabs_send" } satisfies RouteDataProperties, - }), + data: { elevation: 0 } satisfies RouteDataProperties, + }, ], - }), + }, { path: "account-switcher", component: AccountSwitcherComponent, - data: { state: "account-switcher", doNotSaveUrl: true } satisfies RouteDataProperties, + data: { elevation: 4, doNotSaveUrl: true } satisfies RouteDataProperties, }, { path: "trash", component: TrashComponent, canActivate: [authGuard], - data: { state: "trash" } satisfies RouteDataProperties, + data: { elevation: 2 } satisfies RouteDataProperties, }, ]; diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index f3693e4f8a1..7b6e402a90f 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -1,5 +1,6 @@ +// 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 { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router, RouterOutlet } from "@angular/router"; import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs"; @@ -9,9 +10,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; import { UserId } from "@bitwarden/common/types/guid"; @@ -22,11 +21,11 @@ import { ToastOptions, ToastService, } from "@bitwarden/components"; +import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; -import { flagEnabled } from "../platform/flags"; +import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; -import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; import { routerTransition } from "./app-routing.animations"; @@ -36,12 +35,13 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn selector: "app-root", styles: [], animations: [routerTransition], - template: `
    - + template: `
    +
    `, }) export class AppComponent implements OnInit, OnDestroy { private viewCacheService = inject(PopupViewCacheService); + private compactModeService = inject(PopupCompactModeService); private lastActivity: Date; private activeUserId: UserId; @@ -55,7 +55,6 @@ export class AppComponent implements OnInit, OnDestroy { private i18nService: I18nService, private router: Router, private stateService: StateService, - private browserSendStateService: BrowserSendStateService, private vaultBrowserStateService: VaultBrowserStateService, private cipherService: CipherService, private changeDetectorRef: ChangeDetectorRef, @@ -66,36 +65,16 @@ export class AppComponent implements OnInit, OnDestroy { private toastService: ToastService, private accountService: AccountService, private animationControlService: AnimationControlService, - private logService: LogService, - private sdkService: SdkService, - ) { - if (flagEnabled("sdk")) { - // Warn if the SDK for some reason can't be initialized - this.sdkService.supported$.pipe(takeUntilDestroyed()).subscribe({ - next: (supported) => { - if (!supported) { - this.logService.debug("SDK is not supported"); - this.sdkService - .failedToInitialize("popup", undefined) - .catch((e) => this.logService.error(e)); - } else { - this.logService.debug("SDK is supported"); - } - }, - error: (e: unknown) => { - this.sdkService - .failedToInitialize("popup", e as Error) - .catch((e) => this.logService.error(e)); - this.logService.error(e); - }, - }); - } - } + private biometricStateService: BiometricStateService, + private biometricsService: BiometricsService, + ) {} async ngOnInit() { initPopupClosedListener(); await this.viewCacheService.init(); + this.compactModeService.init(); + // 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 await this.clearComponentStates(); @@ -124,7 +103,7 @@ export class AppComponent implements OnInit, OnDestroy { this.messageListener.allMessages$ .pipe( - tap((msg: any) => { + tap(async (msg: any) => { if (msg.command === "doneLoggingOut") { // TODO: PM-8544 - why do we call logout in the popup after receiving the doneLoggingOut message? Hasn't this already completeted logout? this.authService.logOut(async () => { @@ -141,6 +120,7 @@ export class AppComponent implements OnInit, OnDestroy { msg.command === "locked" && (msg.userId == null || msg.userId == this.activeUserId) ) { + await this.biometricsService.setShouldAutopromptNow(false); // 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(["lock"]); @@ -158,6 +138,7 @@ export class AppComponent implements OnInit, OnDestroy { } else if (msg.command === "reloadProcess") { if (this.platformUtilsService.isSafari()) { window.setTimeout(() => { + this.biometricStateService.updateLastProcessReload(); window.location.reload(); }, 2000); } @@ -216,23 +197,12 @@ export class AppComponent implements OnInit, OnDestroy { this.destroy$.complete(); } - getState(outlet: RouterOutlet) { + getRouteElevation(outlet: RouterOutlet) { if (!this.routerAnimations) { return; - } else if (outlet.activatedRouteData.state === "ciphers") { - const routeDirection = - (window as any).routeDirection != null ? (window as any).routeDirection : ""; - return ( - "ciphers_direction=" + - routeDirection + - "_" + - (outlet.activatedRoute.queryParams as any).value.folderId + - "_" + - (outlet.activatedRoute.queryParams as any).value.collectionId - ); - } else { - return outlet.activatedRouteData.state; } + + return outlet.activatedRouteData.elevation; } private async recordActivity() { @@ -273,8 +243,6 @@ export class AppComponent implements OnInit, OnDestroy { await Promise.all([ this.vaultBrowserStateService.setBrowserGroupingsComponentState(null), this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null), - this.browserSendStateService.setBrowserSendComponentState(null), - this.browserSendStateService.setBrowserSendTypeComponentState(null), ]); } diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 7b2d2ba86b8..15a898aef53 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -23,33 +23,20 @@ import { EnvironmentComponent } from "../auth/popup/environment.component"; import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; import { HintComponent } from "../auth/popup/hint.component"; import { HomeComponent } from "../auth/popup/home.component"; -import { LockComponent } from "../auth/popup/lock.component"; -import { LoginDecryptionOptionsComponent } from "../auth/popup/login-decryption-options/login-decryption-options.component"; +import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/popup/login-v1.component"; -import { LoginViaAuthRequestComponent } from "../auth/popup/login-via-auth-request.component"; +import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; import { RegisterComponent } from "../auth/popup/register.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; -import { AccountSecurityComponent as AccountSecurityComponentV1 } from "../auth/popup/settings/account-security-v1.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component"; -import { SsoComponent } from "../auth/popup/sso.component"; +import { SsoComponentV1 } from "../auth/popup/sso-v1.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; -import { Fido2CipherRowV1Component } from "../autofill/popup/fido2/fido2-cipher-row-v1.component"; -import { Fido2CipherRowComponent } from "../autofill/popup/fido2/fido2-cipher-row.component"; -import { Fido2UseBrowserLinkV1Component } from "../autofill/popup/fido2/fido2-use-browser-link-v1.component"; -import { Fido2UseBrowserLinkComponent } from "../autofill/popup/fido2/fido2-use-browser-link.component"; -import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; -import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; -import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; -import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; -import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; -import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; -import { PremiumComponent } from "../billing/popup/settings/premium.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"; @@ -57,39 +44,12 @@ import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.comp import { PopupPageComponent } from "../platform/popup/layout/popup-page.component"; import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component"; import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component"; -import { GeneratorComponent } from "../tools/popup/generator/generator.component"; -import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; -import { SendListComponent } from "../tools/popup/send/components/send-list.component"; -import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; -import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; -import { SendTypeComponent } from "../tools/popup/send/send-type.component"; -import { SettingsComponent } from "../tools/popup/settings/settings.component"; -import { ActionButtonsComponent } from "../vault/popup/components/action-buttons.component"; -import { CipherRowComponent } from "../vault/popup/components/cipher-row.component"; -import { AddEditCustomFieldsComponent } from "../vault/popup/components/vault/add-edit-custom-fields.component"; -import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; -import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; -import { CollectionsComponent } from "../vault/popup/components/vault/collections.component"; -import { CurrentTabComponent } from "../vault/popup/components/vault/current-tab.component"; -import { PasswordHistoryComponent } from "../vault/popup/components/vault/password-history.component"; -import { ShareComponent } from "../vault/popup/components/vault/share.component"; -import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component"; -import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component"; -import { VaultSelectComponent } from "../vault/popup/components/vault/vault-select.component"; -import { ViewCustomFieldsComponent } from "../vault/popup/components/vault/view-custom-fields.component"; -import { ViewComponent } from "../vault/popup/components/vault/view.component"; -import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; -import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; -import { FoldersComponent } from "../vault/popup/settings/folders.component"; -import { SyncComponent } from "../vault/popup/settings/sync.component"; -import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { UserVerificationComponent } from "./components/user-verification.component"; import { ServicesModule } from "./services/services.module"; import { TabsV2Component } from "./tabs-v2.component"; -import { TabsComponent } from "./tabs.component"; // Register the locales for the application import "../platform/popup/locales"; @@ -104,7 +64,7 @@ import "../platform/popup/locales"; maxOpened: 2, autoDismiss: true, closeButton: true, - positionClass: "toast-bottom-full-width", + positionClass: "toast-top-full-width", }), BrowserAnimationsModule, BrowserModule, @@ -117,10 +77,6 @@ import "../platform/popup/locales"; ScrollingModule, ServicesModule, DialogModule, - ExcludedDomainsComponent, - Fido2CipherRowComponent, - Fido2Component, - Fido2UseBrowserLinkComponent, FilePopoutCalloutComponent, AvatarModule, AccountComponent, @@ -138,61 +94,25 @@ import "../platform/popup/locales"; ExtensionAnonLayoutWrapperComponent, ], declarations: [ - ActionButtonsComponent, - AddEditComponent, - AddEditCustomFieldsComponent, AppComponent, - AttachmentsComponent, - CipherRowComponent, - VaultItemsComponent, - CollectionsComponent, ColorPasswordPipe, ColorPasswordCountPipe, - CurrentTabComponent, EnvironmentComponent, - ExcludedDomainsV1Component, - Fido2CipherRowV1Component, - Fido2UseBrowserLinkV1Component, - FolderAddEditComponent, - FoldersComponent, - VaultFilterComponent, HintComponent, HomeComponent, - LockComponent, + LoginViaAuthRequestComponentV1, LoginComponentV1, - LoginViaAuthRequestComponent, - LoginDecryptionOptionsComponent, - NotificationsSettingsV1Component, - AppearanceComponent, - GeneratorComponent, - PasswordGeneratorHistoryComponent, - PasswordHistoryComponent, - PremiumComponent, + LoginDecryptionOptionsComponentV1, RegisterComponent, - SendAddEditComponent, - SendGroupingsComponent, - SendListComponent, - SendTypeComponent, SetPasswordComponent, - SettingsComponent, - VaultSettingsComponent, - ShareComponent, - SsoComponent, - SyncComponent, - TabsComponent, + SsoComponentV1, TabsV2Component, TwoFactorComponent, TwoFactorOptionsComponent, UpdateTempPasswordComponent, UserVerificationComponent, - AccountSecurityComponentV1, VaultTimeoutInputComponent, - ViewComponent, - ViewCustomFieldsComponent, RemovePasswordComponent, - VaultSelectComponent, - Fido2V1Component, - AutofillV1Component, EnvironmentSelectorComponent, ], exports: [], diff --git a/apps/browser/src/popup/images/loading.svg b/apps/browser/src/popup/images/loading.svg index 70763105168..3f2033303db 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/index.html b/apps/browser/src/popup/index.ejs similarity index 84% rename from apps/browser/src/popup/index.html rename to apps/browser/src/popup/index.ejs index e76a8c749fd..20b79b6f067 100644 --- a/apps/browser/src/popup/index.html +++ b/apps/browser/src/popup/index.ejs @@ -1,5 +1,5 @@ - + diff --git a/apps/browser/src/popup/main.ts b/apps/browser/src/popup/main.ts index c98e30e6bca..dadd7917b99 100644 --- a/apps/browser/src/popup/main.ts +++ b/apps/browser/src/popup/main.ts @@ -1,14 +1,18 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/popup.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/tailwind.css"); import { AppModule } from "./app.module"; -// We put this first to minimize the delay in window changing. +// We put these first to minimize the delay in window changing. +PopupSizeService.initBodyWidthFromLocalStorage(); // Should be removed once we deprecate support for Safari 16.0 and older. See Jira ticket [PM-1861] if (BrowserPlatformUtilsService.shouldApplySafariHeightFix(window)) { document.documentElement.classList.add("safari_height_fix"); diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 80c75360872..554396fa038 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -6,6 +6,10 @@ margin: 0; } +html { + overflow: hidden; +} + html, body { font-family: $font-family-sans-serif; @@ -15,8 +19,8 @@ body { } body { - width: 375px !important; - height: 600px !important; + width: 380px; + height: 600px; position: relative; min-height: 100vh; overflow: hidden; @@ -29,18 +33,20 @@ body { } &.body-sm { - width: 375px !important; - height: 500px !important; + height: 500px; } &.body-xs { - width: 375px !important; - height: 300px !important; + height: 400px; + } + + &.body-xxs { + height: 300px; } &.body-full { - width: 100% !important; - height: 100% !important; + width: 100%; + height: 100%; } } @@ -83,9 +89,9 @@ a:not(popup-page a, popup-tab-navigation a) { } } -input, -select, -textarea { +input:not(bit-form-field input, bit-search input), +select:not(bit-form-field select), +textarea:not(bit-form-field textarea) { @include themify($themes) { color: themed("textColor"); background-color: themed("inputBackgroundColor"); @@ -95,7 +101,7 @@ textarea { input, select, textarea, -button { +button:not(bit-chip-select button) { font-size: $font-size-base; font-family: $font-family-sans-serif; } @@ -286,7 +292,7 @@ header:not(bit-callout header, bit-dialog header, popup-page header) { } } - input { + input:not(bit-form-field input) { width: 100%; margin: 0; border: none; diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss index 57bd3e010c8..f61d593c43f 100644 --- a/apps/browser/src/popup/scss/misc.scss +++ b/apps/browser/src/popup/scss/misc.scss @@ -306,7 +306,7 @@ input[type="password"]::-ms-reveal { // contrast against the background, so its inversion is also still readable) // and suppress user selection for most elements (to make it more app-like) -::selection { +:not(bit-form-field input)::selection { @include themify($themes) { color: themed("backgroundColor"); background-color: themed("primaryAccentColor"); diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index bf8f03e7d03..2fd903fedc7 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -301,7 +301,7 @@ app-fido2-v1 { margin-top: 32px; .subtitle { - font-family: Open Sans; + font-family: "DM Sans"; font-size: 24px; font-style: normal; font-weight: 600; diff --git a/apps/browser/src/popup/scss/tailwind.css b/apps/browser/src/popup/scss/tailwind.css index 7e12c1d6770..0d2a88a9667 100644 --- a/apps/browser/src/popup/scss/tailwind.css +++ b/apps/browser/src/popup/scss/tailwind.css @@ -3,3 +3,24 @@ @tailwind utilities; @import "../../../../../libs/components/src/tw-theme.css"; + +@layer components { + /** Safari Support */ + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar { + @apply tw-overflow-auto; + } + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar-thumb { + @apply tw-bg-secondary-500 tw-rounded-lg tw-border-4 tw-border-solid tw-border-transparent tw-bg-clip-content; + } + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar-track { + @apply tw-bg-background-alt; + } + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar-thumb:hover { + @apply tw-bg-secondary-600; + } + + /* FireFox & Chrome support */ + html:not(.browser_safari) .tw-styled-scrollbar { + scrollbar-color: rgb(var(--color-secondary-500)) rgb(var(--color-background-alt)); + } +} diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index a4366a7415e..cfd61cd6a2b 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -2,9 +2,9 @@ $dark-icon-themes: "theme_dark", "theme_solarizedDark", "theme_nord"; -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; -$font-size-base: 14px; +$font-size-base: 16px; $font-size-large: 18px; $font-size-xlarge: 22px; $font-size-xxlarge: 28px; diff --git a/apps/browser/src/popup/services/debounce-navigation.service.ts b/apps/browser/src/popup/services/debounce-navigation.service.ts index b40738f0a80..980350a39d1 100644 --- a/apps/browser/src/popup/services/debounce-navigation.service.ts +++ b/apps/browser/src/popup/services/debounce-navigation.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject, Injectable, OnDestroy } from "@angular/core"; import { CanActivateFn, NavigationEnd, NavigationStart, Router } from "@angular/router"; import { Subscription } from "rxjs"; diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 9e6471eaf28..24661438495 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -1,5 +1,5 @@ import { DOCUMENT } from "@angular/common"; -import { Inject, Injectable } from "@angular/core"; +import { inject, Inject, Injectable } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -10,8 +10,11 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; +import { PopupSizeService } from "../../platform/popup/layout/popup-size.service"; @Injectable() export class InitService { + private sizeService = inject(PopupSizeService); + constructor( private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, @@ -28,13 +31,7 @@ export class InitService { await this.i18nService.init(); this.twoFactorService.init(); - if (!BrowserPopupUtils.inPopup(window)) { - window.document.body.classList.add("body-full"); - } else if (window.screen.availHeight < 600) { - window.document.body.classList.add("body-xs"); - } else if (window.screen.availHeight <= 800) { - window.document.body.classList.add("body-sm"); - } + await this.sizeService.init(); const htmlEl = window.document.documentElement; this.themingService.applyThemeChangesTo(this.document); diff --git a/apps/browser/src/popup/services/popup-close-warning.service.ts b/apps/browser/src/popup/services/popup-close-warning.service.ts index 01e91ecad70..023a96af282 100644 --- a/apps/browser/src/popup/services/popup-close-warning.service.ts +++ b/apps/browser/src/popup/services/popup-close-warning.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { fromEvent, Subscription } from "rxjs"; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 6ef0c278dc3..24d82ab8b67 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -1,4 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core"; +import { Router } from "@angular/router"; import { Subject, merge, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; @@ -21,7 +24,8 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services. import { AnonLayoutWrapperDataService, LoginComponentService, - LockComponentService, + SsoComponentService, + LoginDecryptionOptionsService, } from "@bitwarden/auth/angular"; import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -34,7 +38,6 @@ import { AccountService as AccountServiceAbstraction, } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -103,18 +106,27 @@ import { } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { CompactModeService, DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { BiometricStateService, BiometricsService, KeyService } from "@bitwarden/key-management"; +import { + KdfConfigService, + KeyService, + BiometricsService, + DefaultKeyService, +} from "@bitwarden/key-management"; +import { LockComponentService } from "@bitwarden/key-management/angular"; import { PasswordRepromptService } from "@bitwarden/vault"; import { ForegroundLockService } from "../../auth/popup/accounts/foreground-lock.service"; import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service"; +import { ExtensionSsoComponentService } from "../../auth/popup/login/extension-sso-component.service"; +import { ExtensionLoginDecryptionOptionsService } from "../../auth/popup/login-decryption-options/extension-login-decryption-options.service"; import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service"; import AutofillService from "../../autofill/services/autofill.service"; +import { InlineMenuFieldQualificationService } from "../../autofill/services/inline-menu-field-qualification.service"; import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics"; -import { BrowserKeyService } from "../../key-management/browser-key.service"; +import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator"; /* eslint-disable no-restricted-imports */ @@ -122,6 +134,7 @@ import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sen /* eslint-enable no-restricted-imports */ import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document"; import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service"; +import { PopupCompactModeService } from "../../platform/popup/layout/popup-compact-mode.service"; import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { PopupViewCacheService } from "../../platform/popup/view-cache/popup-view-cache.service"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; @@ -137,9 +150,7 @@ import { BrowserStorageServiceProvider } from "../../platform/storage/browser-st import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { ForegroundSyncService } from "../../platform/sync/foreground-sync.service"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; -import { ExtensionLockComponentService } from "../../services/extension-lock-component.service"; import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service"; -import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service"; import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service"; @@ -167,6 +178,7 @@ const safeProviders: SafeProvider[] = [ safeProvider(DebounceNavigationService), safeProvider(DialogService), safeProvider(PopupCloseWarningService), + safeProvider(InlineMenuFieldQualificationService), safeProvider({ provide: DEFAULT_VAULT_TIMEOUT, useValue: VaultTimeoutStringType.OnRestart, @@ -219,11 +231,9 @@ const safeProviders: SafeProvider[] = [ stateService: StateService, accountService: AccountServiceAbstraction, stateProvider: StateProvider, - biometricStateService: BiometricStateService, - biometricsService: BiometricsService, kdfConfigService: KdfConfigService, ) => { - const keyService = new BrowserKeyService( + const keyService = new DefaultKeyService( pinService, masterPasswordService, keyGenerationService, @@ -234,8 +244,6 @@ const safeProviders: SafeProvider[] = [ stateService, accountService, stateProvider, - biometricStateService, - biometricsService, kdfConfigService, ); new ContainerService(keyService, encryptService).attachToGlobal(self); @@ -252,8 +260,6 @@ const safeProviders: SafeProvider[] = [ StateService, AccountServiceAbstraction, StateProvider, - BiometricStateService, - BiometricsService, KdfConfigService, ], }), @@ -314,7 +320,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DomainSettingsService, useClass: DefaultDomainSettingsService, - deps: [StateProvider], + deps: [StateProvider, ConfigService], }), safeProvider({ provide: AbstractStorageService, @@ -352,7 +358,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ScriptInjectorService, useClass: BrowserScriptInjectorService, - deps: [PlatformUtilsService, LogService], + deps: [DomainSettingsService, PlatformUtilsService, LogService], }), safeProvider({ provide: VaultTimeoutService, @@ -459,11 +465,6 @@ const safeProviders: SafeProvider[] = [ useClass: UserNotificationSettingsService, deps: [StateProvider], }), - safeProvider({ - provide: BrowserSendStateService, - useClass: BrowserSendStateService, - deps: [StateProvider], - }), safeProvider({ provide: MessageListener, useFactory: (subject: Subject>>, ngZone: NgZone) => @@ -566,8 +567,9 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: SdkClientFactory, - useClass: flagEnabled("sdk") ? BrowserSdkClientFactory : NoopSdkClientFactory, - deps: [], + useFactory: (logService: LogService) => + flagEnabled("sdk") ? new BrowserSdkClientFactory(logService) : new NoopSdkClientFactory(), + deps: [LogService], }), safeProvider({ provide: LoginEmailService, @@ -579,6 +581,21 @@ const safeProviders: SafeProvider[] = [ useClass: ExtensionAnonLayoutWrapperDataService, deps: [], }), + safeProvider({ + provide: CompactModeService, + useExisting: PopupCompactModeService, + deps: [], + }), + safeProvider({ + provide: SsoComponentService, + useClass: ExtensionSsoComponentService, + deps: [SyncService, AuthService, EnvironmentService, I18nServiceAbstraction, LogService], + }), + safeProvider({ + provide: LoginDecryptionOptionsService, + useClass: ExtensionLoginDecryptionOptionsService, + deps: [MessagingServiceAbstraction, Router], + }), ]; @NgModule({ diff --git a/apps/browser/src/popup/tabs.component.html b/apps/browser/src/popup/tabs.component.html deleted file mode 100644 index fd04967b914..00000000000 --- a/apps/browser/src/popup/tabs.component.html +++ /dev/null @@ -1,57 +0,0 @@ -
    - - -
    diff --git a/apps/browser/src/popup/tabs.component.ts b/apps/browser/src/popup/tabs.component.ts deleted file mode 100644 index 7546c9ca13b..00000000000 --- a/apps/browser/src/popup/tabs.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from "@angular/core"; - -import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; - -@Component({ - selector: "app-tabs", - templateUrl: "tabs.component.html", -}) -export class TabsComponent implements OnInit { - showCurrentTab = true; - - ngOnInit() { - this.showCurrentTab = !BrowserPopupUtils.inPopout(window); - } -} diff --git a/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift b/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift index 1768ce6b15f..58d95f959be 100644 --- a/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift +++ b/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift @@ -86,8 +86,203 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { context.completeRequest(returningItems: [response], completionHandler: nil) } return - case "biometricUnlock": + case "authenticateWithBiometrics": + let messageId = message?["messageId"] as? Int + let laContext = LAContext() + guard let accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .userPresence], nil) else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "authenticateWithBiometrics", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + break + } + laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "authenticate") { (success, error) in + if success { + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "authenticateWithBiometrics", + "response": true, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ]] + } else { + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "authenticateWithBiometrics", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ]] + } + context.completeRequest(returningItems: [response], completionHandler: nil) + } + return + case "getBiometricsStatus": + let messageId = message?["messageId"] as? Int + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatus", + "response": BiometricsStatus.Available.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil); + break + case "unlockWithBiometricsForUser": + let messageId = message?["messageId"] as? Int + var error: NSError? + let laContext = LAContext() + + laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) + + if let e = error, e.code != kLAErrorBiometryLockout { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil) + break + } + + guard let accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .userPresence], nil) else { + let messageId = message?["messageId"] as? Int + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil) + break + } + laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "unlock your vault") { (success, error) in + if success { + guard let userId = message?["userId"] as? String else { + return + } + let passwordName = userId + "_user_biometric" + var passwordLength: UInt32 = 0 + var passwordPtr: UnsafeMutableRawPointer? = nil + + var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil) + if status != errSecSuccess { + let fallbackName = "key" + status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil) + } + + if status == errSecSuccess { + let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String? + SecKeychainItemFreeContent(nil, passwordPtr) + + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": true, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "userKeyB64": result!.replacingOccurrences(of: "\"", with: ""), + "messageId": messageId, + ], + ]] + } else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": true, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + } + } + + context.completeRequest(returningItems: [response], completionHandler: nil) + } + return + case "getBiometricsStatusForUser": + let messageId = message?["messageId"] as? Int + let laContext = LAContext() + if !laContext.isBiometricsAvailable() { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatusForUser", + "response": BiometricsStatus.HardwareUnavailable.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil) + break + } + + guard let userId = message?["userId"] as? String else { + return + } + let passwordName = userId + "_user_biometric" + var passwordLength: UInt32 = 0 + var passwordPtr: UnsafeMutableRawPointer? = nil + + var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil) + if status != errSecSuccess { + let fallbackName = "key" + status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil) + } + + if status == errSecSuccess { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatusForUser", + "response": BiometricsStatus.Available.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + } else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatusForUser", + "response": BiometricsStatus.NotEnabledInConnectedDesktopApp.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + } + break + case "biometricUnlock": + var error: NSError? let laContext = LAContext() if(!laContext.isBiometricsAvailable()){ @@ -115,7 +310,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { ] break } - laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "Bitwarden Safari Extension") { (success, error) in + laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "Biometric Unlock") { (success, error) in if success { guard let userId = message?["userId"] as? String else { return @@ -157,7 +352,6 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { context.completeRequest(returningItems: [response], completionHandler: nil) } - return case "biometricUnlockAvailable": let laContext = LAContext() @@ -228,3 +422,15 @@ class DownloadFileMessage: Decodable, Encodable { class DownloadFileMessageBlobOptions: Decodable, Encodable { var type: String? } + +enum BiometricsStatus : Int { + case Available = 0 + case UnlockNeeded = 1 + case HardwareUnavailable = 2 + case AutoSetupNeeded = 3 + case ManualSetupNeeded = 4 + case PlatformUnsupported = 5 + case DesktopDisconnected = 6 + case NotEnabledLocally = 7 + case NotEnabledInConnectedDesktopApp = 8 +} diff --git a/apps/browser/src/services/extension-lock-component.service.ts b/apps/browser/src/services/extension-lock-component.service.ts deleted file mode 100644 index 28fe21ede69..00000000000 --- a/apps/browser/src/services/extension-lock-component.service.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { inject } from "@angular/core"; -import { combineLatest, defer, map, Observable } from "rxjs"; - -import { - BiometricsDisableReason, - LockComponentService, - UnlockOptions, -} from "@bitwarden/auth/angular"; -import { - PinServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; - -import { BiometricErrors, BiometricErrorTypes } from "../models/biometricErrors"; -import { BrowserRouterService } from "../platform/popup/services/browser-router.service"; - -export class ExtensionLockComponentService implements LockComponentService { - private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); - private readonly platformUtilsService = inject(PlatformUtilsService); - private readonly biometricsService = inject(BiometricsService); - private readonly pinService = inject(PinServiceAbstraction); - private readonly vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); - private readonly keyService = inject(KeyService); - private readonly routerService = inject(BrowserRouterService); - - getPreviousUrl(): string | null { - return this.routerService.getPreviousUrl(); - } - - getBiometricsError(error: any): string | null { - const biometricsError = BiometricErrors[error?.message as BiometricErrorTypes]; - - if (!biometricsError) { - return null; - } - - return biometricsError.description; - } - - async isWindowVisible(): Promise { - throw new Error("Method not implemented."); - } - - getBiometricsUnlockBtnText(): string { - return "unlockWithBiometrics"; - } - - private async isBiometricLockSet(userId: UserId): Promise { - const biometricLockSet = await this.vaultTimeoutSettingsService.isBiometricLockSet(userId); - const hasBiometricEncryptedUserKeyStored = await this.keyService.hasUserKeyStored( - KeySuffixOptions.Biometric, - userId, - ); - const platformSupportsSecureStorage = this.platformUtilsService.supportsSecureStorage(); - - return ( - biometricLockSet && (hasBiometricEncryptedUserKeyStored || !platformSupportsSecureStorage) - ); - } - - private getBiometricsDisabledReason( - osSupportsBiometric: boolean, - biometricLockSet: boolean, - ): BiometricsDisableReason | null { - if (!osSupportsBiometric) { - return BiometricsDisableReason.NotSupportedOnOperatingSystem; - } else if (!biometricLockSet) { - return BiometricsDisableReason.EncryptedKeysUnavailable; - } - - return null; - } - - getAvailableUnlockOptions$(userId: UserId): Observable { - return combineLatest([ - // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(() => this.biometricsService.supportsBiometric()), - defer(() => this.isBiometricLockSet(userId)), - this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), - defer(() => this.pinService.isPinDecryptionAvailable(userId)), - ]).pipe( - map( - ([ - supportsBiometric, - isBiometricsLockSet, - userDecryptionOptions, - pinDecryptionAvailable, - ]) => { - const disableReason = this.getBiometricsDisabledReason( - supportsBiometric, - isBiometricsLockSet, - ); - - const unlockOpts: UnlockOptions = { - masterPassword: { - enabled: userDecryptionOptions.hasMasterPassword, - }, - pin: { - enabled: pinDecryptionAvailable, - }, - biometrics: { - enabled: supportsBiometric && isBiometricsLockSet, - disableReason: disableReason, - }, - }; - return unlockOpts; - }, - ), - ); - } -} diff --git a/apps/browser/src/services/families-policy.service.spec.ts b/apps/browser/src/services/families-policy.service.spec.ts new file mode 100644 index 00000000000..19291bcd825 --- /dev/null +++ b/apps/browser/src/services/families-policy.service.spec.ts @@ -0,0 +1,83 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; + +import { FamiliesPolicyService } from "./families-policy.service"; // Adjust the import as necessary + +describe("FamiliesPolicyService", () => { + let service: FamiliesPolicyService; + let organizationService: MockProxy; + let policyService: MockProxy; + + beforeEach(() => { + organizationService = mock(); + policyService = mock(); + + TestBed.configureTestingModule({ + providers: [ + FamiliesPolicyService, + { provide: OrganizationService, useValue: organizationService }, + { provide: PolicyService, useValue: policyService }, + ], + }); + + service = TestBed.inject(FamiliesPolicyService); + }); + + it("should return false when there are no enterprise organizations", async () => { + jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(false)); + + const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$()); + expect(result).toBe(false); + }); + + it("should return true when the policy is enabled for the one enterprise organization", async () => { + jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true)); + + const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[]; + organizationService.getAll$.mockReturnValue(of(organizations)); + + const policies = [{ organizationId: "org1", enabled: true }] as Policy[]; + policyService.getAll$.mockReturnValue(of(policies)); + + const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$()); + expect(result).toBe(true); + }); + + it("should return false when the policy is not enabled for the one enterprise organization", async () => { + jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true)); + + const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[]; + organizationService.getAll$.mockReturnValue(of(organizations)); + + const policies = [{ organizationId: "org1", enabled: false }] as Policy[]; + policyService.getAll$.mockReturnValue(of(policies)); + + const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$()); + expect(result).toBe(false); + }); + + it("should return true when there is exactly one enterprise organization that can manage sponsorships", async () => { + const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[]; + organizationService.getAll$.mockReturnValue(of(organizations)); + + const result = await firstValueFrom(service.hasSingleEnterpriseOrg$()); + expect(result).toBe(true); + }); + + it("should return false when there are multiple organizations that can manage sponsorships", async () => { + const organizations = [ + { id: "org1", canManageSponsorships: true }, + { id: "org2", canManageSponsorships: true }, + ] as Organization[]; + organizationService.getAll$.mockReturnValue(of(organizations)); + + const result = await firstValueFrom(service.hasSingleEnterpriseOrg$()); + expect(result).toBe(false); + }); +}); diff --git a/apps/browser/src/services/families-policy.service.ts b/apps/browser/src/services/families-policy.service.ts new file mode 100644 index 00000000000..426f39dcfd0 --- /dev/null +++ b/apps/browser/src/services/families-policy.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from "@angular/core"; +import { map, Observable, of, switchMap } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; + +@Injectable({ providedIn: "root" }) +export class FamiliesPolicyService { + constructor( + private policyService: PolicyService, + private organizationService: OrganizationService, + ) {} + + hasSingleEnterpriseOrg$(): Observable { + // Retrieve all organizations the user is part of + return this.organizationService.getAll$().pipe( + map((organizations) => { + // Filter to only those organizations that can manage sponsorships + const sponsorshipOrgs = organizations.filter((org) => org.canManageSponsorships); + + // Check if there is exactly one organization that can manage sponsorships. + // This is important because users that are part of multiple organizations + // may always access free bitwarden family menu. We want to restrict access + // to the policy only when there is a single enterprise organization and the free family policy is turn. + return sponsorshipOrgs.length === 1; + }), + ); + } + + isFreeFamilyPolicyEnabled$(): Observable { + return this.hasSingleEnterpriseOrg$().pipe( + switchMap((hasSingleEnterpriseOrg) => { + if (!hasSingleEnterpriseOrg) { + return of(false); + } + return this.organizationService.getAll$().pipe( + map((organizations) => organizations.find((org) => org.canManageSponsorships)?.id), + switchMap((enterpriseOrgId) => + this.policyService + .getAll$(PolicyType.FreeFamiliesSponsorshipPolicy) + .pipe( + map( + (policies) => + policies.find((policy) => policy.organizationId === enterpriseOrgId)?.enabled ?? + false, + ), + ), + ), + ); + }), + ); + } +} diff --git a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts b/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts index 462e2149e88..0d49595d2eb 100644 --- a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts +++ b/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts @@ -1,6 +1,8 @@ -import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/src/abstractions/vault-timeout/vault-timeout.service"; -import { MessagingService } from "@bitwarden/common/src/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/src/types/guid"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { UserId } from "@bitwarden/common/types/guid"; export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService { constructor(protected messagingService: MessagingService) {} diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts index 7b356b18fd5..409fac9790f 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -1,9 +1,10 @@ import { mock } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { of } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; 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"; @@ -23,18 +24,10 @@ import { FilelessImportPort, FilelessImportType } from "../enums/fileless-import import FilelessImporterBackground from "./fileless-importer.background"; -jest.mock("rxjs", () => { - const rxjs = jest.requireActual("rxjs"); - const { firstValueFrom } = rxjs; - return { - ...rxjs, - firstValueFrom: jest.fn(firstValueFrom), - }; -}); - describe("FilelessImporterBackground ", () => { let filelessImporterBackground: FilelessImporterBackground; const configService = mock(); + const domainSettingsService = mock(); const authService = mock(); const policyService = mock(); const notificationBackground = mock(); @@ -43,9 +36,16 @@ describe("FilelessImporterBackground ", () => { const platformUtilsService = mock(); const logService = mock(); let scriptInjectorService: BrowserScriptInjectorService; + let tabMock: chrome.tabs.Tab; beforeEach(() => { - scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); + domainSettingsService.blockedInteractionsUris$ = of({}); + policyService.policyAppliesToActiveUser$.mockImplementation(() => of(true)); + scriptInjectorService = new BrowserScriptInjectorService( + domainSettingsService, + platformUtilsService, + logService, + ); filelessImporterBackground = new FilelessImporterBackground( configService, authService, @@ -75,12 +75,13 @@ describe("FilelessImporterBackground ", () => { beforeEach(() => { lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter); + tabMock = lpImporterPort.sender.tab; + jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null); jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked); jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true); jest.spyOn(filelessImporterBackground as any, "removeIndividualVault"); - (firstValueFrom as jest.Mock).mockResolvedValue(false); }); it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => { @@ -105,8 +106,6 @@ describe("FilelessImporterBackground ", () => { }); it("posts a message to the port indicating that the fileless import feature is disabled if the user's policy removes individual vaults", async () => { - (firstValueFrom as jest.Mock).mockResolvedValue(true); - triggerRuntimeOnConnectEvent(lpImporterPort); await flushPromises(); @@ -129,6 +128,8 @@ describe("FilelessImporterBackground ", () => { }); it("posts a message to the port indicating that the fileless import feature is enabled", async () => { + policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); + triggerRuntimeOnConnectEvent(lpImporterPort); await flushPromises(); @@ -139,6 +140,7 @@ describe("FilelessImporterBackground ", () => { }); it("triggers an injection of the `lp-suppress-import-download.js` script in manifest v3", async () => { + policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); manifestVersionSpy.mockReturnValue(3); triggerRuntimeOnConnectEvent(lpImporterPort); @@ -152,6 +154,7 @@ describe("FilelessImporterBackground ", () => { }); it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => { + policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); manifestVersionSpy.mockReturnValue(2); triggerRuntimeOnConnectEvent(lpImporterPort); @@ -170,9 +173,10 @@ describe("FilelessImporterBackground ", () => { let lpImporterPort: chrome.runtime.Port; beforeEach(async () => { + policyService.policyAppliesToActiveUser$.mockImplementation(() => of(false)); jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked); jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true); - (firstValueFrom as jest.Mock).mockResolvedValue(false); + triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.NotificationBar)); triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.LpImporter)); await flushPromises(); diff --git a/apps/browser/src/tools/background/fileless-importer.background.ts b/apps/browser/src/tools/background/fileless-importer.background.ts index fed5541f520..21d597ec8ae 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts index 432754ab91c..21fa44b8d3f 100644 --- a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts +++ b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts @@ -12,6 +12,8 @@ describe("LpFilelessImporter", () => { chrome.runtime.connect = jest.fn(() => portSpy); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-fileless-importer"); lpFilelessImporter = (globalThis as any).lpFilelessImporter; }); diff --git a/apps/browser/src/tools/content/lp-fileless-importer.ts b/apps/browser/src/tools/content/lp-fileless-importer.ts index 6f091ecf5a5..497a499b337 100644 --- a/apps/browser/src/tools/content/lp-fileless-importer.ts +++ b/apps/browser/src/tools/content/lp-fileless-importer.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FilelessImportPort } from "../enums/fileless-import.enums"; import { diff --git a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts index 95b49ea00ec..8479235cc17 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts @@ -7,6 +7,8 @@ describe("LP Suppress Import Download for Manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts index bfff3787506..ff0ed381599 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts @@ -10,6 +10,8 @@ describe("LP Suppress Import Download", () => { jest.spyOn(Element.prototype, "appendChild"); jest.spyOn(window, "addEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download"); anchor = document.createElement("a"); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.ts b/apps/browser/src/tools/content/lp-suppress-import-download.ts index 486d391279d..1d5d449d199 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /** * Handles intercepting the injection of the CSV download link, and ensures the * download of the script is suppressed until the user opts to download the file. 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 9218403b133..491e33c5738 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; 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 dd65bb96878..7cbc7c82dd7 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; diff --git a/apps/browser/src/tools/popup/generator/generator.component.html b/apps/browser/src/tools/popup/generator/generator.component.html deleted file mode 100644 index d92d32a5623..00000000000 --- a/apps/browser/src/tools/popup/generator/generator.component.html +++ /dev/null @@ -1,588 +0,0 @@ - -
    - - -
    -

    - {{ "generator" | i18n }} -

    -
    - -
    -
    -
    - - {{ "passwordGeneratorPolicyInEffect" | i18n }} - -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -

    - {{ "options" | i18n }} -

    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - - -
    -
    - {{ "passwordMinLength" | i18n }} - - {{ passwordOptionsMinLengthForReader$ | async }} - - {{ passwordOptions.minLength }} -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -

    - {{ "options" | i18n }} -

    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    - -
    - - -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    diff --git a/apps/browser/src/tools/popup/generator/generator.component.ts b/apps/browser/src/tools/popup/generator/generator.component.ts deleted file mode 100644 index e6d9276116c..00000000000 --- a/apps/browser/src/tools/popup/generator/generator.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, NgZone, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; -import { ToastService } from "@bitwarden/components"; -import { - PasswordGenerationServiceAbstraction, - UsernameGenerationServiceAbstraction, -} from "@bitwarden/generator-legacy"; - -@Component({ - selector: "app-generator", - templateUrl: "generator.component.html", -}) -export class GeneratorComponent extends BaseGeneratorComponent implements OnInit { - private addEditCipherInfo: AddEditCipherInfo; - private cipherState: CipherView; - private cipherService: CipherService; - - constructor( - passwordGenerationService: PasswordGenerationServiceAbstraction, - usernameGenerationService: UsernameGenerationServiceAbstraction, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - accountService: AccountService, - cipherService: CipherService, - route: ActivatedRoute, - logService: LogService, - ngZone: NgZone, - private location: Location, - toastService: ToastService, - ) { - super( - passwordGenerationService, - usernameGenerationService, - platformUtilsService, - accountService, - i18nService, - logService, - route, - ngZone, - window, - toastService, - ); - this.cipherService = cipherService; - } - - async ngOnInit() { - this.addEditCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$); - if (this.addEditCipherInfo != null) { - this.cipherState = this.addEditCipherInfo.cipher; - } - this.comingFromAddEdit = this.cipherState != null; - if (this.cipherState?.login?.hasUris) { - this.usernameWebsite = this.cipherState.login.uris[0].hostname; - } - await super.ngOnInit(); - } - - select() { - super.select(); - if (this.type === "password") { - this.cipherState.login.password = this.password; - } else if (this.type === "username") { - this.cipherState.login.username = this.username; - } - this.addEditCipherInfo.cipher = this.cipherState; - // 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.cipherService.setAddEditCipherInfo(this.addEditCipherInfo); - this.close(); - } - - close() { - this.location.back(); - } -} diff --git a/apps/browser/src/tools/popup/generator/password-generator-history.component.html b/apps/browser/src/tools/popup/generator/password-generator-history.component.html deleted file mode 100644 index 8f4a246fc5e..00000000000 --- a/apps/browser/src/tools/popup/generator/password-generator-history.component.html +++ /dev/null @@ -1,48 +0,0 @@ -
    -
    - -
    -

    - {{ "passwordHistory" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    - {{ h.date | date: "medium" }} -
    -
    -
    - -
    -
    -
    -
    -
    -

    {{ "noPasswordsInList" | i18n }}

    -
    -
    diff --git a/apps/browser/src/tools/popup/generator/password-generator-history.component.ts b/apps/browser/src/tools/popup/generator/password-generator-history.component.ts deleted file mode 100644 index 2436ae51a7d..00000000000 --- a/apps/browser/src/tools/popup/generator/password-generator-history.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Location } from "@angular/common"; -import { Component } from "@angular/core"; - -import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "@bitwarden/angular/tools/generator/components/password-generator-history.component"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -@Component({ - selector: "app-password-generator-history", - templateUrl: "password-generator-history.component.html", -}) -export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { - constructor( - passwordGenerationService: PasswordGenerationServiceAbstraction, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - private location: Location, - toastService: ToastService, - ) { - super(passwordGenerationService, platformUtilsService, i18nService, window, toastService); - } - - close() { - this.location.back(); - } -} 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 d1005883651..d69f2568858 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, Location } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 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 98b09d380e4..7191040ac6f 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 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 d535bbd86e3..4266dd3914e 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; 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 23cc692a598..d51bda45b55 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 @@ -6,7 +6,7 @@ -
    + {{ "sendDisabledWarning" | i18n }} @@ -14,7 +14,7 @@ -
    +
    { CurrentAccountComponent, ], providers: [ - { provide: AccountService, useValue: mock() }, + { + provide: AccountService, + useValue: { + activeAccount$: of({ + id: "123", + email: "test@email.com", + emailVerified: true, + name: "Test User", + }), + }, + }, { provide: AuthService, useValue: mock() }, { provide: AvatarService, useValue: mock() }, { diff --git a/apps/browser/src/tools/popup/send/components/send-list.component.html b/apps/browser/src/tools/popup/send/components/send-list.component.html deleted file mode 100644 index 05c8e3e3754..00000000000 --- a/apps/browser/src/tools/popup/send/components/send-list.component.html +++ /dev/null @@ -1,98 +0,0 @@ -
    - -
    - - - -
    -
    diff --git a/apps/browser/src/tools/popup/send/components/send-list.component.ts b/apps/browser/src/tools/popup/send/components/send-list.component.ts deleted file mode 100644 index 032ffaa57cb..00000000000 --- a/apps/browser/src/tools/popup/send/components/send-list.component.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Component, EventEmitter, Input, Output } from "@angular/core"; - -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; - -@Component({ - selector: "app-send-list", - templateUrl: "send-list.component.html", -}) -export class SendListComponent { - @Input() sends: SendView[]; - @Input() title: string; - @Input() disabledByPolicy = false; - @Output() onSelected = new EventEmitter(); - @Output() onCopySendLink = new EventEmitter(); - @Output() onRemovePassword = new EventEmitter(); - @Output() onDeleteSend = new EventEmitter(); - - sendType = SendType; - - selectSend(s: SendView) { - this.onSelected.emit(s); - } - - copySendLink(s: SendView) { - this.onCopySendLink.emit(s); - } - - removePassword(s: SendView) { - this.onRemovePassword.emit(s); - } - - delete(s: SendView) { - this.onDeleteSend.emit(s); - } -} diff --git a/apps/browser/src/tools/popup/send/send-add-edit.component.html b/apps/browser/src/tools/popup/send/send-add-edit.component.html deleted file mode 100644 index 38c8a8175bb..00000000000 --- a/apps/browser/src/tools/popup/send/send-add-edit.component.html +++ /dev/null @@ -1,411 +0,0 @@ -
    -
    -
    - -
    -

    - {{ title }} -

    -
    - -
    -
    -
    - - - {{ "sendDisabledWarning" | i18n }} - - - {{ "sendOptionsPolicyInEffect" | i18n }} - - - - -
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - -
    {{ send.file.fileName }} ({{ send.file.sizeName }})
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    -
    - - -
    -
    -
    - -
    -

    - {{ "share" | i18n }} -

    -
    - -
    - - -
    -
    -
    - -
    -

    - -

    -
    -
    - -
    -
    - -
    - - -
    -
    - -
    -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - -
    -
    -
    -
    - - -
    - -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    - - - -
    -
    - -
    -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    diff --git a/apps/browser/src/tools/popup/send/send-add-edit.component.ts b/apps/browser/src/tools/popup/send/send-add-edit.component.ts deleted file mode 100644 index 569a9a15a5e..00000000000 --- a/apps/browser/src/tools/popup/send/send-add-edit.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { DatePipe, Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { 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 { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { FilePopoutUtilsService } from "../services/file-popout-utils.service"; - -@Component({ - selector: "app-send-add-edit", - templateUrl: "send-add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class SendAddEditComponent extends BaseAddEditComponent implements OnInit { - // Options header - showOptions = false; - // File visibility - isFirefox = false; - inPopout = false; - showFileSelector = false; - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - messagingService: MessagingService, - policyService: PolicyService, - environmentService: EnvironmentService, - datePipe: DatePipe, - sendService: SendService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - formBuilder: FormBuilder, - private filePopoutUtilsService: FilePopoutUtilsService, - billingAccountProfileStateService: BillingAccountProfileStateService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - i18nService, - platformUtilsService, - environmentService, - datePipe, - sendService, - messagingService, - policyService, - logService, - stateService, - sendApiService, - dialogService, - formBuilder, - billingAccountProfileStateService, - accountService, - toastService, - ); - } - - popOutWindow() { - // 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 - BrowserPopupUtils.openCurrentPagePopout(window); - } - - async ngOnInit() { - // File visibility - this.showFileSelector = - !this.editMode && !this.filePopoutUtilsService.showFilePopoutMessage(window); - this.inPopout = BrowserPopupUtils.inPopout(window); - this.isFirefox = this.platformUtilsService.isFirefox(); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.sendId) { - this.sendId = params.sendId; - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - await super.ngOnInit(); - }); - - window.setTimeout(() => { - if (!this.editMode) { - document.getElementById("name").focus(); - } - }, 200); - } - - async submit(): Promise { - if (await super.submit()) { - this.cancel(); - return true; - } - - return false; - } - - async delete(): Promise { - if (await super.delete()) { - this.cancel(); - return true; - } - - return false; - } - - cancel() { - // If true, the window was pop'd out on the add-send page. location.back will not work - const isPopup = (window as any)?.previousPopupUrl?.startsWith("/add-send") ?? false; - if (!isPopup) { - // 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(["tabs/send"]); - } else { - this.location.back(); - } - } -} diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.html b/apps/browser/src/tools/popup/send/send-groupings.component.html deleted file mode 100644 index 213afdfa227..00000000000 --- a/apps/browser/src/tools/popup/send/send-groupings.component.html +++ /dev/null @@ -1,119 +0,0 @@ - -
    - -
    -

    {{ "send" | i18n }}

    - -
    - -
    -
    -
    - - {{ "sendDisabledWarning" | i18n }} - -
    - - - -

    {{ "noItemsInList" | i18n }}

    - -
    -
    - -
    -

    - {{ "types" | i18n }} -

    -
    - - -
    -
    -
    -

    - {{ "allSends" | i18n }} -
    {{ sends.length }}
    -

    -
    - -
    -
    -
    - -
    -

    {{ "noItemsInList" | i18n }}

    -
    -
    -
    - - -
    -
    -
    -
    diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.ts b/apps/browser/src/tools/popup/send/send-groupings.component.ts deleted file mode 100644 index 87de6af31cf..00000000000 --- a/apps/browser/src/tools/popup/send/send-groupings.component.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; - -import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { BrowserSendStateService } from "../services/browser-send-state.service"; - -const ComponentId = "SendComponent"; - -@Component({ - selector: "app-send-groupings", - templateUrl: "send-groupings.component.html", -}) -export class SendGroupingsComponent extends BaseSendComponent implements OnInit, OnDestroy { - // Header - showLeftHeader = true; - // State Handling - state: BrowserSendComponentState; - private loadedTimeout: number; - - constructor( - sendService: SendService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - ngZone: NgZone, - policyService: PolicyService, - searchService: SearchService, - private stateService: BrowserSendStateService, - private router: Router, - private syncService: SyncService, - private changeDetectorRef: ChangeDetectorRef, - private broadcasterService: BroadcasterService, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - toastService: ToastService, - ) { - super( - sendService, - i18nService, - platformUtilsService, - environmentService, - ngZone, - searchService, - policyService, - logService, - sendApiService, - dialogService, - toastService, - ); - this.onSuccessfulLoad = async () => { - this.selectAll(); - }; - } - - async ngOnInit() { - // Determine Header details - this.showLeftHeader = !( - BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() - ); - // Clear state of Send Type Component - await this.stateService.setBrowserSendTypeComponentState(null); - // Let super class finish - await super.ngOnInit(); - // Handle State Restore if necessary - const restoredScopeState = await this.restoreState(); - if (this.state?.searchText != null) { - this.searchText = this.state.searchText; - } - - if (!this.syncService.syncInProgress) { - // 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.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - // 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.load(); - } - }, 5000); - } - - if (!this.syncService.syncInProgress || restoredScopeState) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY); - } - - // Load all sends if sync completed in background - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - window.setTimeout(() => { - // 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.load(); - }, 500); - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // Remove timeout - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); - } - // Save state - // 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.saveState(); - // Unsubscribe - this.broadcasterService.unsubscribe(ComponentId); - } - - async selectType(type: SendType) { - // 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(["/send-type"], { queryParams: { type: type } }); - } - - async selectSend(s: SendView) { - // 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(["/edit-send"], { queryParams: { sendId: s.id } }); - } - - async addSend() { - if (this.disableSend) { - return; - } - // 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(["/add-send"]); - } - - async removePassword(s: SendView): Promise { - if (this.disableSend) { - return; - } - // 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.removePassword(s); - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.isSearchable); - } - - getSendCount(sends: SendView[], type: SendType): number { - return sends.filter((s) => s.type === type).length; - } - - private async saveState() { - this.state = Object.assign(new BrowserSendComponentState(), { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - sends: this.sends, - }); - await this.stateService.setBrowserSendComponentState(this.state); - } - - private async restoreState(): Promise { - this.state = await this.stateService.getBrowserSendComponentState(); - if (this.state == null) { - return false; - } - - if (this.state.sends != null) { - this.sends = this.state.sends; - } - - return true; - } -} diff --git a/apps/browser/src/tools/popup/send/send-type.component.html b/apps/browser/src/tools/popup/send/send-type.component.html deleted file mode 100644 index 0ad6bed2881..00000000000 --- a/apps/browser/src/tools/popup/send/send-type.component.html +++ /dev/null @@ -1,68 +0,0 @@ -
    -
    - -
    -

    {{ "send" | i18n }}

    - -
    - -
    -
    -
    - - {{ "sendDisabledWarning" | i18n }} - -
    - - -

    {{ "noItemsInList" | i18n }}

    - -
    -
    -
    -

    - {{ groupingTitle }} - {{ filteredSends.length }} -

    -
    - - -
    -
    -
    diff --git a/apps/browser/src/tools/popup/send/send-type.component.ts b/apps/browser/src/tools/popup/send/send-type.component.ts deleted file mode 100644 index 8329831e0bc..00000000000 --- a/apps/browser/src/tools/popup/send/send-type.component.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { BrowserSendStateService } from "../services/browser-send-state.service"; - -const ComponentId = "SendTypeComponent"; - -@Component({ - selector: "app-send-type", - templateUrl: "send-type.component.html", -}) -export class SendTypeComponent extends BaseSendComponent implements OnInit, OnDestroy { - groupingTitle: string; - // State Handling - state: BrowserComponentState; - private refreshTimeout: number; - private applySavedState = true; - - constructor( - sendService: SendService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - ngZone: NgZone, - policyService: PolicyService, - searchService: SearchService, - private stateService: BrowserSendStateService, - private route: ActivatedRoute, - private location: Location, - private changeDetectorRef: ChangeDetectorRef, - private broadcasterService: BroadcasterService, - private router: Router, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - toastService: ToastService, - ) { - super( - sendService, - i18nService, - platformUtilsService, - environmentService, - ngZone, - searchService, - policyService, - logService, - sendApiService, - dialogService, - toastService, - ); - this.onSuccessfulLoad = async () => { - this.selectType(this.type); - }; - this.applySavedState = - (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith("/send-type"); - } - - async ngOnInit() { - // Let super class finish - await super.ngOnInit(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (this.applySavedState) { - this.state = await this.stateService.getBrowserSendTypeComponentState(); - if (this.state?.searchText != null) { - this.searchText = this.state.searchText; - } - } - - if (params.type != null) { - this.type = parseInt(params.type, null); - switch (this.type) { - case SendType.Text: - this.groupingTitle = this.i18nService.t("sendTypeText"); - break; - case SendType.File: - this.groupingTitle = this.i18nService.t("sendTypeFile"); - break; - default: - break; - } - await this.load((s) => s.type === this.type); - } - - // Restore state and remove reference - if (this.applySavedState && this.state != null) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY); - } - // 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.stateService.setBrowserSendTypeComponentState(null); - }); - - // Refresh Send list if sync completed in background - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - this.refreshTimeout = window.setTimeout(() => { - // 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.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // Remove timeout - if (this.refreshTimeout != null) { - window.clearTimeout(this.refreshTimeout); - } - // Save state - // 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.saveState(); - // Unsubscribe - this.broadcasterService.unsubscribe(ComponentId); - } - - async selectSend(s: SendView) { - // 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(["/edit-send"], { queryParams: { sendId: s.id } }); - } - - async addSend() { - if (this.disableSend) { - return; - } - // 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(["/add-send"], { queryParams: { type: this.type } }); - } - - async removePassword(s: SendView): Promise { - if (this.disableSend) { - return; - } - // 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.removePassword(s); - } - - back() { - (window as any).routeDirection = "b"; - this.location.back(); - } - - private async saveState() { - this.state = { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - }; - await this.stateService.setBrowserSendTypeComponentState(this.state); - } -} diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts deleted file mode 100644 index 6f0ae1455ad..00000000000 --- a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - FakeAccountService, - mockAccountServiceWith, -} from "@bitwarden/common/../spec/fake-account-service"; -import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; -import { awaitAsync } from "@bitwarden/common/../spec/utils"; - -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -import { BrowserSendStateService } from "./browser-send-state.service"; - -describe("Browser Send State Service", () => { - let stateProvider: FakeStateProvider; - - let accountService: FakeAccountService; - let stateService: BrowserSendStateService; - const mockUserId = Utils.newGuid() as UserId; - - beforeEach(() => { - accountService = mockAccountServiceWith(mockUserId); - stateProvider = new FakeStateProvider(accountService); - - stateService = new BrowserSendStateService(stateProvider); - }); - - describe("getBrowserSendComponentState", () => { - it("should return BrowserSendComponentState", async () => { - const state = new BrowserSendComponentState(); - state.scrollY = 0; - state.searchText = "test"; - - await stateService.setBrowserSendComponentState(state); - - await awaitAsync(); - - const actual = await stateService.getBrowserSendComponentState(); - expect(actual).toStrictEqual(state); - }); - }); - - describe("getBrowserSendTypeComponentState", () => { - it("should return BrowserComponentState", async () => { - const state = new BrowserComponentState(); - state.scrollY = 0; - state.searchText = "test"; - - await stateService.setBrowserSendTypeComponentState(state); - - await awaitAsync(); - - const actual = await stateService.getBrowserSendTypeComponentState(); - expect(actual).toStrictEqual(state); - }); - }); -}); diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.ts deleted file mode 100644 index 11e71c9b20f..00000000000 --- a/apps/browser/src/tools/popup/services/browser-send-state.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Observable, firstValueFrom } from "rxjs"; - -import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; - -/** Get or set the active user's component state for the Send browser component - */ -export class BrowserSendStateService { - /** Observable that contains the current state for active user Sends including the send data and type counts - * along with the search text and scroll position - */ - browserSendComponentState$: Observable; - - /** Observable that contains the current state for active user Sends that only includes the search text - * and scroll position - */ - browserSendTypeComponentState$: Observable; - - private activeUserBrowserSendComponentState: ActiveUserState; - private activeUserBrowserSendTypeComponentState: ActiveUserState; - - constructor(protected stateProvider: StateProvider) { - this.activeUserBrowserSendComponentState = this.stateProvider.getActive(BROWSER_SEND_COMPONENT); - this.browserSendComponentState$ = this.activeUserBrowserSendComponentState.state$; - - this.activeUserBrowserSendTypeComponentState = this.stateProvider.getActive( - BROWSER_SEND_TYPE_COMPONENT, - ); - this.browserSendTypeComponentState$ = this.activeUserBrowserSendTypeComponentState.state$; - } - - /** Get the active user's browser send component state - * @returns { BrowserSendComponentState } contains the sends and type counts along with the scroll position and search text for the - * send component on the browser - */ - async getBrowserSendComponentState(): Promise { - return await firstValueFrom(this.browserSendComponentState$); - } - - /** Set the active user's browser send component state - * @param { BrowserSendComponentState } value sets the sends along with the scroll position and search text for - * the send component on the browser - */ - async setBrowserSendComponentState(value: BrowserSendComponentState): Promise { - await this.activeUserBrowserSendComponentState.update(() => value, { - shouldUpdate: (current) => !(current == null && value == null), - }); - } - - /** Get the active user's browser component state - * @returns { BrowserComponentState } contains the scroll position and search text for the sends menu on the browser - */ - async getBrowserSendTypeComponentState(): Promise { - return await firstValueFrom(this.browserSendTypeComponentState$); - } - - /** Set the active user's browser component state - * @param { BrowserComponentState } value set the scroll position and search text for the send component on the browser - */ - async setBrowserSendTypeComponentState(value: BrowserComponentState): Promise { - await this.activeUserBrowserSendTypeComponentState.update(() => value, { - shouldUpdate: (current) => !(current == null && value == null), - }); - } -} diff --git a/apps/browser/src/tools/popup/services/file-popout-utils.service.ts b/apps/browser/src/tools/popup/services/file-popout-utils.service.ts index 65a311e47c3..fa72e411316 100644 --- a/apps/browser/src/tools/popup/services/file-popout-utils.service.ts +++ b/apps/browser/src/tools/popup/services/file-popout-utils.service.ts @@ -49,7 +49,7 @@ export class FilePopoutUtilsService { } /** - * Determines whether to show a file popout callout message for Chromium-based browsers in Linux and Mac OS X Big Sur + * Determines whether to show a file popout callout message for Chromium-based browsers in Linux and Mac OS X * @param win - The window context in which the check should be performed. * @returns True if the extension is not in a sidebar or popout; otherwise, false. */ @@ -66,8 +66,6 @@ export class FilePopoutUtilsService { } private isUnsupportedMac(win: Window): boolean { - return ( - this.platformUtilsService.isChrome() && win?.navigator?.appVersion.includes("Mac OS X 11") - ); + return this.platformUtilsService.isChrome() && win?.navigator?.appVersion.includes("Mac OS X"); } } diff --git a/apps/browser/src/tools/popup/services/key-definitions.spec.ts b/apps/browser/src/tools/popup/services/key-definitions.spec.ts deleted file mode 100644 index 7517771669c..00000000000 --- a/apps/browser/src/tools/popup/services/key-definitions.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Jsonify } from "type-fest"; - -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; - -describe("Key definitions", () => { - describe("BROWSER_SEND_COMPONENT", () => { - it("should deserialize BrowserSendComponentState", () => { - const keyDef = BROWSER_SEND_COMPONENT; - - const expectedState = { - scrollY: 0, - searchText: "test", - }; - - const result = keyDef.deserializer( - JSON.parse(JSON.stringify(expectedState)) as Jsonify, - ); - - expect(result).toEqual(expectedState); - }); - }); - - describe("BROWSER_SEND_TYPE_COMPONENT", () => { - it("should deserialize BrowserComponentState", () => { - const keyDef = BROWSER_SEND_TYPE_COMPONENT; - - const expectedState = { - scrollY: 0, - searchText: "test", - }; - - const result = keyDef.deserializer(JSON.parse(JSON.stringify(expectedState))); - - expect(result).toEqual(expectedState); - }); - }); -}); diff --git a/apps/browser/src/tools/popup/services/key-definitions.ts b/apps/browser/src/tools/popup/services/key-definitions.ts deleted file mode 100644 index b4ccd991e7b..00000000000 --- a/apps/browser/src/tools/popup/services/key-definitions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Jsonify } from "type-fest"; - -import { BROWSER_SEND_MEMORY, UserKeyDefinition } from "@bitwarden/common/platform/state"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -export const BROWSER_SEND_COMPONENT = new UserKeyDefinition( - BROWSER_SEND_MEMORY, - "browser_send_component", - { - deserializer: (obj: Jsonify) => - BrowserSendComponentState.fromJSON(obj), - clearOn: ["logout", "lock"], - }, -); - -export const BROWSER_SEND_TYPE_COMPONENT = new UserKeyDefinition( - BROWSER_SEND_MEMORY, - "browser_send_type_component", - { - deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), - clearOn: ["logout", "lock"], - }, -); 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 bad39a53d31..eceeaf19cb7 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,6 +6,7 @@

    © Bitwarden Inc. 2015-{{ year }}

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

    +

    SDK: {{ sdkVersion$ | async }}

    {{ "serverVersion" | i18n }}: {{ data.serverConfig?.version }} 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 0467debdfb5..f730fef24b3 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 @@ -6,6 +6,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { ButtonModule, DialogModule } from "@bitwarden/components"; @Component({ @@ -22,10 +23,13 @@ export class AboutDialogComponent { this.environmentService.environment$.pipe(map((env) => env.isCloud())), ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); + protected sdkVersion$ = this.sdkService.version$; + constructor( private configService: ConfigService, private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, + private sdkService: SdkService, ) { this.version$ = defer(() => this.platformUtilsService.getApplicationVersion()); } diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page.component.html b/apps/browser/src/tools/popup/settings/about-page/about-page.component.html deleted file mode 100644 index 7537c75bd9e..00000000000 --- a/apps/browser/src/tools/popup/settings/about-page/about-page.component.html +++ /dev/null @@ -1,63 +0,0 @@ -

    -
    - -
    -

    - {{ "about" | i18n }} -

    -
    - -
    -
    -
    -
    -
    - - - - - -
    -
    -
    diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page.component.ts b/apps/browser/src/tools/popup/settings/about-page/about-page.component.ts deleted file mode 100644 index 7c3e87a92fb..00000000000 --- a/apps/browser/src/tools/popup/settings/about-page/about-page.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { RouterModule } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { DeviceType } from "@bitwarden/common/enums"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; - -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; -import { AboutDialogComponent } from "../about-dialog/about-dialog.component"; - -const RateUrls = { - [DeviceType.ChromeExtension]: - "https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews", - [DeviceType.FirefoxExtension]: - "https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews", - [DeviceType.OperaExtension]: - "https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container", - [DeviceType.EdgeExtension]: - "https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh", - [DeviceType.VivaldiExtension]: - "https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews", - [DeviceType.SafariExtension]: "https://apps.apple.com/app/bitwarden/id1352778147", -}; - -@Component({ - templateUrl: "about-page.component.html", - standalone: true, - imports: [CommonModule, JslibModule, RouterModule, PopOutComponent], -}) -export class AboutPageComponent { - constructor( - private dialogService: DialogService, - private environmentService: EnvironmentService, - private platformUtilsService: PlatformUtilsService, - ) {} - - about() { - this.dialogService.open(AboutDialogComponent); - } - - async launchHelp() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToHelpCenter" }, - content: { key: "continueToHelpCenterDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/help/"); - } - } - - async openWebVault() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToWebApp" }, - content: { key: "continueToWebAppDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const env = await firstValueFrom(this.environmentService.environment$); - const url = env.getWebVaultUrl(); - await BrowserApi.createNewTab(url); - } - } - - async rate() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBrowserExtensionStore" }, - content: { key: "continueToBrowserExtensionStoreDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const deviceType = this.platformUtilsService.getDevice(); - await BrowserApi.createNewTab((RateUrls as any)[deviceType]); - } - } -} diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.html b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.html index 9322ab5113e..a2d01ce752e 100644 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.html +++ b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.html @@ -12,7 +12,12 @@ - + -
    -

    - {{ "moreFromBitwarden" | i18n }} -

    -
    - -
    -
-
-
-
-
- -
- - - - - -
-
-
diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts deleted file mode 100644 index a9e9e797bf3..00000000000 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { RouterModule } from "@angular/router"; -import { Observable, firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { DialogService } from "@bitwarden/components"; - -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; - -@Component({ - templateUrl: "more-from-bitwarden-page.component.html", - standalone: true, - imports: [CommonModule, JslibModule, RouterModule, PopOutComponent], -}) -export class MoreFromBitwardenPageComponent { - canAccessPremium$: Observable; - - constructor( - private dialogService: DialogService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private environmentService: EnvironmentService, - ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; - } - - async openFreeBitwardenFamiliesPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToWebApp" }, - content: { key: "freeBitwardenFamiliesPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const env = await firstValueFrom(this.environmentService.environment$); - const url = env.getWebVaultUrl(); - await BrowserApi.createNewTab(url + "/#/settings/sponsored-families"); - } - } - - async openBitwardenForBusinessPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "bitwardenForBusinessPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/business/"); - } - } - - async openAuthenticatorPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "continueToAuthenticatorPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/authenticator"); - } - } - - async openSecretsManagerPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "continueToSecretsManagerPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/secrets-manager"); - } - } - - async openPasswordlessDotDevPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "continueToPasswordlessDotDevPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/passwordless"); - } - } -} 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 cbb66cbcf5a..86131176a6e 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 @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; +import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; @@ -16,7 +16,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page standalone: true, imports: [ CommonModule, - RouterLink, JslibModule, DialogModule, AsyncActionsModule, diff --git a/apps/browser/src/tools/popup/settings/export/export-browser.component.html b/apps/browser/src/tools/popup/settings/export/export-browser.component.html deleted file mode 100644 index bccde32a68d..00000000000 --- a/apps/browser/src/tools/popup/settings/export/export-browser.component.html +++ /dev/null @@ -1,26 +0,0 @@ -
-
- -
-

- {{ "exportVault" | i18n }} -

-
- -
-
-
-
- -
-
diff --git a/apps/browser/src/tools/popup/settings/export/export-browser.component.ts b/apps/browser/src/tools/popup/settings/export/export-browser.component.ts deleted file mode 100644 index 3125e0a2934..00000000000 --- a/apps/browser/src/tools/popup/settings/export/export-browser.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; -import { ExportComponent } from "@bitwarden/vault-export-ui"; - -@Component({ - templateUrl: "export-browser.component.html", - standalone: true, - imports: [ - CommonModule, - RouterLink, - JslibModule, - DialogModule, - AsyncActionsModule, - ButtonModule, - ExportComponent, - ], -}) -export class ExportBrowserComponent { - /** - * Used to control the disabled state of the Submit button - * Gets set indirectly by the disabled state being emitted from the sub-form when thier form gets disabled or the submit button is clicked - */ - protected disabled = false; - - /** - * Used to control the disabled state of the Submit button - * Gets set indirectly by the loading state being emitted from the sub-form when their form is loading or finished loading - */ - protected loading = false; - - constructor(private router: Router) {} - - protected async onSuccessfulExport(organizationId: string): Promise { - await this.router.navigate(["/vault-settings"]); - } -} 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 16759057ed5..66cb5c62f48 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 @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; +import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; @@ -16,7 +16,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page standalone: true, imports: [ CommonModule, - RouterLink, JslibModule, DialogModule, AsyncActionsModule, diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.html b/apps/browser/src/tools/popup/settings/import/import-browser.component.html deleted file mode 100644 index 67b5eb348ae..00000000000 --- a/apps/browser/src/tools/popup/settings/import/import-browser.component.html +++ /dev/null @@ -1,26 +0,0 @@ -
-
- -
-

- {{ "importData" | i18n }} -

-
- -
-
-
-
- -
-
diff --git a/apps/browser/src/tools/popup/settings/import/import-browser.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser.component.ts deleted file mode 100644 index 7ee4877ce1a..00000000000 --- a/apps/browser/src/tools/popup/settings/import/import-browser.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; -import { ImportComponent } from "@bitwarden/importer/ui"; - -@Component({ - templateUrl: "import-browser.component.html", - standalone: true, - imports: [ - CommonModule, - RouterLink, - JslibModule, - DialogModule, - AsyncActionsModule, - ButtonModule, - ImportComponent, - ], -}) -export class ImportBrowserComponent { - protected disabled = false; - protected loading = false; - - constructor(private router: Router) {} - - protected async onSuccessfulImport(organizationId: string): Promise { - // 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(["/tabs/settings"]); - } -} diff --git a/apps/browser/src/tools/popup/settings/settings.component.html b/apps/browser/src/tools/popup/settings/settings.component.html deleted file mode 100644 index c547229653e..00000000000 --- a/apps/browser/src/tools/popup/settings/settings.component.html +++ /dev/null @@ -1,63 +0,0 @@ - -
-

- {{ "settings" | i18n }} -

-
- -
-
-
-
-
- - - - - - -
-
-
diff --git a/apps/browser/src/tools/popup/settings/settings.component.ts b/apps/browser/src/tools/popup/settings/settings.component.ts deleted file mode 100644 index 973efc72038..00000000000 --- a/apps/browser/src/tools/popup/settings/settings.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "tools-settings", - templateUrl: "settings.component.html", -}) -export class SettingsComponent { - constructor() {} -} diff --git a/apps/browser/src/vault/guards/clear-vault-state.guard.ts b/apps/browser/src/vault/guards/clear-vault-state.guard.ts index 2b43f1ecbd3..b212c55d833 100644 --- a/apps/browser/src/vault/guards/clear-vault-state.guard.ts +++ b/apps/browser/src/vault/guards/clear-vault-state.guard.ts @@ -1,7 +1,7 @@ import { inject } from "@angular/core"; import { CanDeactivateFn } from "@angular/router"; -import { VaultV2Component } from "../popup/components/vault/vault-v2.component"; +import { VaultV2Component } from "../popup/components/vault-v2/vault-v2.component"; import { VaultPopupItemsService } from "../popup/services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../popup/services/vault-popup-list-filters.service"; diff --git a/apps/browser/src/vault/popup/components/action-buttons.component.html b/apps/browser/src/vault/popup/components/action-buttons.component.html deleted file mode 100644 index f63c1f1ac32..00000000000 --- a/apps/browser/src/vault/popup/components/action-buttons.component.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - diff --git a/apps/browser/src/vault/popup/components/action-buttons.component.ts b/apps/browser/src/vault/popup/components/action-buttons.component.ts deleted file mode 100644 index b0e7b318d2e..00000000000 --- a/apps/browser/src/vault/popup/components/action-buttons.component.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { Subject, takeUntil } from "rxjs"; - -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EventType } from "@bitwarden/common/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -@Component({ - selector: "app-action-buttons", - templateUrl: "action-buttons.component.html", -}) -export class ActionButtonsComponent implements OnInit, OnDestroy { - @Output() onView = new EventEmitter(); - @Output() launchEvent = new EventEmitter(); - @Input() cipher: CipherView; - @Input() showView = false; - - cipherType = CipherType; - userHasPremiumAccess = false; - - private componentIsDestroyed$ = new Subject(); - - constructor( - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private eventCollectionService: EventCollectionService, - private totpService: TotpServiceAbstraction, - private passwordRepromptService: PasswordRepromptService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - ) {} - - ngOnInit() { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.componentIsDestroyed$)) - .subscribe((canAccessPremium: boolean) => { - this.userHasPremiumAccess = canAccessPremium; - }); - } - - ngOnDestroy() { - this.componentIsDestroyed$.next(true); - this.componentIsDestroyed$.complete(); - } - - launchCipher() { - this.launchEvent.emit(this.cipher); - } - - async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) { - if ( - this.cipher.reprompt !== CipherRepromptType.None && - this.passwordRepromptService.protectedFields().includes(aType) && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - return; - } - - if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) { - return; - } else if (aType === "TOTP") { - value = await this.totpService.getCode(value); - } - - if (!cipher.viewPassword) { - return; - } - - this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); - - if (typeI18nKey === "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.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); - } else if (typeI18nKey === "verificationCodeTotp") { - // 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.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, cipher.id); - } else if (typeI18nKey === "securityCode") { - // 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.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); - } - } - - displayTotpCopyButton(cipher: CipherView) { - return ( - (cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess) - ); - } - - view() { - this.onView.emit(this.cipher); - } -} diff --git a/apps/browser/src/vault/popup/components/cipher-row.component.html b/apps/browser/src/vault/popup/components/cipher-row.component.html deleted file mode 100644 index 8ac9147cb92..00000000000 --- a/apps/browser/src/vault/popup/components/cipher-row.component.html +++ /dev/null @@ -1,51 +0,0 @@ -
-
- - - -
-
diff --git a/apps/browser/src/vault/popup/components/cipher-row.component.ts b/apps/browser/src/vault/popup/components/cipher-row.component.ts deleted file mode 100644 index e2992ad1840..00000000000 --- a/apps/browser/src/vault/popup/components/cipher-row.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Component, EventEmitter, Input, Output } from "@angular/core"; - -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; - -@Component({ - selector: "app-cipher-row", - templateUrl: "cipher-row.component.html", -}) -export class CipherRowComponent { - @Output() onSelected = new EventEmitter(); - @Output() launchEvent = new EventEmitter(); - @Output() onView = new EventEmitter(); - @Input() cipher: CipherView; - @Input() last: boolean; - @Input() showView = false; - @Input() title: string; - - selectCipher(c: CipherView) { - this.onSelected.emit(c); - } - - launchCipher(c: CipherView) { - this.launchEvent.emit(c); - } - - viewCipher(c: CipherView) { - this.onView.emit(c); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts index 4e222a554f7..cbec7903031 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts @@ -171,7 +171,7 @@ describe("AddEditFolderDialogComponent", () => { it("deletes the folder", async () => { await component.deleteFolder(); - expect(deleteFolder).toHaveBeenCalledWith(folderView.id); + expect(deleteFolder).toHaveBeenCalledWith(folderView.id, ""); expect(showToast).toHaveBeenCalledWith({ variant: "success", title: null, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index c0cbf877480..a50403cea2d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { @@ -11,7 +13,7 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -65,6 +67,7 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { name: ["", Validators.required], }); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroyRef = inject(DestroyRef); constructor( @@ -112,10 +115,10 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { this.folder.name = this.folderForm.controls.name.value; try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(this.folder, userKey); - await this.folderApiService.save(folder); + await this.folderApiService.save(folder, activeUserId); this.toastService.showToast({ variant: "success", @@ -142,7 +145,8 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { } try { - await this.folderApiService.delete(this.folder.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.folderApiService.delete(this.folder.id, activeUserId); this.toastService.showToast({ variant: "success", title: null, 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 8d95acbce9d..2d8c4857c1c 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -324,15 +326,15 @@ export class AddEditV2Component implements OnInit { switch (type) { case CipherType.Login: - return this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLocaleLowerCase()); + return this.i18nService.t(partOne, this.i18nService.t("typeLogin")); case CipherType.Card: - return this.i18nService.t(partOne, this.i18nService.t("typeCard").toLocaleLowerCase()); + return this.i18nService.t(partOne, this.i18nService.t("typeCard")); case CipherType.Identity: - return this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLocaleLowerCase()); + return this.i18nService.t(partOne, this.i18nService.t("typeIdentity")); case CipherType.SecureNote: - return this.i18nService.t(partOne, this.i18nService.t("note").toLocaleLowerCase()); + return this.i18nService.t(partOne, this.i18nService.t("note")); case CipherType.SshKey: - return this.i18nService.t(partOne, this.i18nService.t("typeSshKey").toLocaleLowerCase()); + return this.i18nService.t(partOne, this.i18nService.t("typeSshKey")); } } } 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 202103c16d3..51ebe9bdb62 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, Location } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 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 09762767c81..32d446daf75 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; 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 8c1e0641b03..4f6c4aa07cf 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 @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -10,7 +10,6 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -55,7 +54,14 @@ describe("OpenAttachmentsComponent", () => { const showFilePopoutMessage = jest.fn().mockReturnValue(false); const mockUserId = Utils.newGuid() as UserId; - const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const accountService = { + activeAccount$: of({ + id: mockUserId, + email: "test@email.com", + emailVerified: true, + name: "Test User", + }), + }; beforeEach(async () => { openCurrentPagePopout.mockClear(); @@ -63,6 +69,7 @@ describe("OpenAttachmentsComponent", () => { showToast.mockClear(); getOrganization.mockClear(); showFilePopoutMessage.mockClear(); + hasPremiumFromAnySource$.next(true); await TestBed.configureTestingModule({ imports: [OpenAttachmentsComponent, RouterTestingModule], @@ -96,7 +103,7 @@ describe("OpenAttachmentsComponent", () => { }).compileComponents(); }); - beforeEach(() => { + beforeEach(async () => { fixture = TestBed.createComponent(OpenAttachmentsComponent); component = fixture.componentInstance; component.cipherId = "5555-444-3333" as CipherId; @@ -107,7 +114,7 @@ describe("OpenAttachmentsComponent", () => { it("opens attachments in new popout", async () => { showFilePopoutMessage.mockReturnValue(true); - + component.canAccessAttachments = true; await component.ngOnInit(); await component.openAttachments(); @@ -120,7 +127,7 @@ describe("OpenAttachmentsComponent", () => { it("opens attachments in same window", async () => { showFilePopoutMessage.mockReturnValue(false); - + component.canAccessAttachments = true; await component.ngOnInit(); await component.openAttachments(); 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 118510695c5..5e27ccd5c41 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 @@ -1,8 +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, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom, map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -52,8 +54,13 @@ export class OpenAttachmentsComponent implements OnInit { private filePopoutUtilsService: FilePopoutUtilsService, private accountService: AccountService, ) { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntilDestroyed()) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntilDestroyed(), + ) .subscribe((canAccessPremium) => { this.canAccessAttachments = canAccessPremium; }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html index 52ae387e8b9..047d168ecbb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -1,9 +1,10 @@ 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 8e72d84053d..deba204bd71 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 @@ -1,8 +1,9 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { combineLatest, map, Observable } from "rxjs"; +import { Component, OnInit } from "@angular/core"; +import { combineLatest, firstValueFrom, 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, @@ -31,7 +32,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/ selector: "app-autofill-vault-list-items", templateUrl: "autofill-vault-list-items.component.html", }) -export class AutofillVaultListItemsComponent { +export class AutofillVaultListItemsComponent implements OnInit { /** * The list of ciphers that can be used to autofill the current page. * @protected @@ -45,6 +46,8 @@ export class AutofillVaultListItemsComponent { */ protected showRefresh: boolean = BrowserPopupUtils.inSidebar(window); + clickItemsToAutofillVaultView = false; + /** * Observable that determines whether the empty autofill tip should be shown. * The tip is shown when there are no login ciphers to autofill, no filter is applied, and autofill is allowed in @@ -65,10 +68,17 @@ export class AutofillVaultListItemsComponent { constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupAutofillService: VaultPopupAutofillService, + private vaultSettingsService: VaultSettingsService, ) { // TODO: Migrate logic to show Autofill policy toast PM-8144 } + async ngOnInit() { + this.clickItemsToAutofillVaultView = await firstValueFrom( + this.vaultSettingsService.clickItemsToAutofillVaultView$, + ); + } + /** * Refreshes the current tab to re-populate the autofill ciphers. * @protected diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html new file mode 100644 index 00000000000..05db600bd5a --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html @@ -0,0 +1,10 @@ + + {{ "autofillBlockedNoticeV2" | i18n }} + + {{ "autofillBlockedNoticeGuidance" | i18n }} + + 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 new file mode 100644 index 00000000000..3a17825f4fb --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -0,0 +1,53 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Observable } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + BannerModule, + IconButtonModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; + +const blockedURISettingsRoute = "/blocked-domains"; + +@Component({ + standalone: true, + imports: [ + BannerModule, + CommonModule, + IconButtonModule, + JslibModule, + LinkModule, + RouterModule, + TypographyModule, + ], + selector: "blocked-injection-banner", + templateUrl: "blocked-injection-banner.component.html", +}) +export class BlockedInjectionBanner implements OnInit { + /** + * Flag indicating that the banner should be shown + */ + protected showCurrentTabIsBlockedBanner$: Observable = + this.vaultPopupAutofillService.showCurrentTabIsBlockedBanner$; + + /** + * Hostname for current tab + */ + protected currentTabHostname?: string; + + blockedURISettingsRoute: string = blockedURISettingsRoute; + + constructor(private vaultPopupAutofillService: VaultPopupAutofillService) {} + + async ngOnInit() {} + + async handleCurrentTabIsBlockedBannerDismiss() { + await this.vaultPopupAutofillService.dismissCurrentTabIsBlockedBanner(); + } +} 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 973b1f9f1a4..fbfebe8efff 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 @@ -1,53 +1,117 @@ - - - - - - - - + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -18,6 +19,11 @@
+ + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 8ce3bcd2b60..5d3dee9018e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, Input, OnInit } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; @@ -44,6 +46,13 @@ export class ItemMoreOptionsComponent implements OnInit { return this._cipher$.value; } + /** + * Flag to show view item menu option. Used when something else is + * assigned as the primary action for the item, such as autofill. + */ + @Input({ transform: booleanAttribute }) + showViewOption: boolean; + /** * Flag to hide the autofill menu options. Used for items that are * already in the autofill list suggestion. @@ -109,6 +118,16 @@ export class ItemMoreOptionsComponent implements OnInit { await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, false); } + async onView() { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(this.cipher); + if (!repromptPassed) { + return; + } + await this.router.navigate(["/view-cipher"], { + queryParams: { cipherId: this.cipher.id, type: this.cipher.type }, + }); + } + /** * Toggles the favorite status of the cipher and updates it on the server. */ diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html index 78403784f46..4d617ff7786 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html @@ -3,22 +3,39 @@ {{ "new" | i18n }} - + {{ "typeLogin" | i18n }} - + {{ "typeCard" | i18n }} - + {{ "typeIdentity" | i18n }} - + {{ "note" | i18n }} + + + {{ "typeSshKey" | i18n }} + +

+ {{ supportingText }} +

+
+ {{ numberOfAppliedFilters$ | async }} +
+ + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts new file mode 100644 index 00000000000..38ec6056d19 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -0,0 +1,162 @@ +import { CommonModule } from "@angular/common"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormBuilder } from "@angular/forms"; +import { By } from "@angular/platform-browser"; +import { ActivatedRoute } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject, Subject } from "rxjs"; + +import { CollectionService } from "@bitwarden/admin-console/common"; +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { MessageSender } from "@bitwarden/common/platform/messaging"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { SyncService } from "@bitwarden/common/platform/sync"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { AutofillService } from "../../../../../autofill/services/abstractions/autofill.service"; +import { VaultPopupItemsService } from "../../../../../vault/popup/services/vault-popup-items.service"; +import { + PopupListFilter, + VaultPopupListFiltersService, +} from "../../../../../vault/popup/services/vault-popup-list-filters.service"; + +import { VaultHeaderV2Component } from "./vault-header-v2.component"; + +describe("VaultHeaderV2Component", () => { + let component: VaultHeaderV2Component; + let fixture: ComponentFixture; + + const emptyForm: PopupListFilter = { + organization: null, + collection: null, + folder: null, + cipherType: null, + }; + + const numberOfAppliedFilters$ = new BehaviorSubject(0); + const state$ = new Subject(); + + // Mock state provider update + const update = jest.fn().mockResolvedValue(undefined); + + /** When it exists, returns the notification badge debug element */ + const getBadge = () => fixture.debugElement.query(By.css('[data-testid="filter-badge"]')); + + beforeEach(async () => { + update.mockClear(); + + await TestBed.configureTestingModule({ + imports: [VaultHeaderV2Component, CommonModule], + providers: [ + { + provide: CipherService, + useValue: mock({ cipherViews$: new BehaviorSubject([]) }), + }, + { provide: VaultSettingsService, useValue: mock() }, + { provide: FolderService, useValue: mock() }, + { provide: OrganizationService, useValue: mock() }, + { provide: CollectionService, useValue: mock() }, + { provide: PolicyService, useValue: mock() }, + { provide: SearchService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: AutofillService, useValue: mock() }, + { provide: PasswordRepromptService, useValue: mock() }, + { provide: MessageSender, useValue: mock() }, + { provide: AccountService, useValue: mock() }, + { provide: LogService, useValue: mock() }, + { + provide: VaultPopupItemsService, + useValue: mock({ latestSearchText$: new BehaviorSubject("") }), + }, + { + provide: SyncService, + useValue: mock({ activeUserLastSync$: () => new Subject() }), + }, + { provide: ActivatedRoute, useValue: { queryParams: new BehaviorSubject({}) } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + { + provide: VaultPopupListFiltersService, + useValue: { + numberOfAppliedFilters$, + filters$: new BehaviorSubject(emptyForm), + filterForm: new FormBuilder().group(emptyForm), + filterVisibilityState$: state$, + updateFilterVisibility: update, + }, + }, + { + provide: StateProvider, + useValue: { getGlobal: () => ({ state$, update }) }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(VaultHeaderV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("does not show filter badge when no filters are selected", () => { + state$.next(false); + numberOfAppliedFilters$.next(0); + fixture.detectChanges(); + + expect(getBadge()).toBeNull(); + }); + + it("does not show filter badge when disclosure is open", () => { + state$.next(true); + numberOfAppliedFilters$.next(1); + fixture.detectChanges(); + + expect(getBadge()).toBeNull(); + }); + + it("shows the notification badge when there are populated filters and the disclosure is closed", async () => { + state$.next(false); + numberOfAppliedFilters$.next(1); + fixture.detectChanges(); + + expect(getBadge()).not.toBeNull(); + }); + + it("displays the number of filters populated", () => { + numberOfAppliedFilters$.next(1); + state$.next(false); + fixture.detectChanges(); + + expect(getBadge().nativeElement.textContent.trim()).toBe("1"); + + numberOfAppliedFilters$.next(2); + + fixture.detectChanges(); + + expect(getBadge().nativeElement.textContent.trim()).toBe("2"); + + numberOfAppliedFilters$.next(4); + + fixture.detectChanges(); + + expect(getBadge().nativeElement.textContent.trim()).toBe("4"); + }); + + it("defaults the initial state to true", (done) => { + // The initial value of the `state$` variable above is undefined + component["initialDisclosureVisibility$"].subscribe((initialVisibility) => { + expect(initialVisibility).toBeTrue(); + done(); + }); + + // Update the state to null + state$.next(null); + }); +}); 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 new file mode 100644 index 00000000000..3b9dc9a1647 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -0,0 +1,74 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; +import { Component, inject, NgZone, ViewChild } from "@angular/core"; +import { combineLatest, map, take } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DisclosureTriggerForDirective, IconButtonModule } from "@bitwarden/components"; + +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { DisclosureComponent } from "../../../../../../../../libs/components/src/disclosure/disclosure.component"; +import { runInsideAngular } from "../../../../../platform/browser/run-inside-angular.operator"; +import { VaultPopupListFiltersService } from "../../../../../vault/popup/services/vault-popup-list-filters.service"; +import { VaultListFiltersComponent } from "../vault-list-filters/vault-list-filters.component"; +import { VaultV2SearchComponent } from "../vault-search/vault-v2-search.component"; + +@Component({ + selector: "app-vault-header-v2", + templateUrl: "vault-header-v2.component.html", + standalone: true, + imports: [ + VaultV2SearchComponent, + VaultListFiltersComponent, + DisclosureComponent, + IconButtonModule, + DisclosureTriggerForDirective, + CommonModule, + JslibModule, + ], +}) +export class VaultHeaderV2Component { + @ViewChild(DisclosureComponent) disclosure: DisclosureComponent; + + /** Emits the visibility status of the disclosure component. */ + protected isDisclosureShown$ = this.vaultPopupListFiltersService.filterVisibilityState$.pipe( + runInsideAngular(inject(NgZone)), // Browser state updates can happen outside of `ngZone` + map((v) => v ?? true), + ); + + // Only use the first value to avoid an infinite loop from two-way binding + protected initialDisclosureVisibility$ = this.isDisclosureShown$.pipe(take(1)); + + protected numberOfAppliedFilters$ = this.vaultPopupListFiltersService.numberOfAppliedFilters$; + + /** Emits true when the number of filters badge should be applied. */ + protected showBadge$ = combineLatest([ + this.numberOfAppliedFilters$, + this.isDisclosureShown$, + ]).pipe(map(([numberOfFilters, disclosureShown]) => numberOfFilters !== 0 && !disclosureShown)); + + protected buttonSupportingText$ = this.numberOfAppliedFilters$.pipe( + map((numberOfFilters) => { + if (numberOfFilters === 0) { + return null; + } + if (numberOfFilters === 1) { + return this.i18nService.t("filterApplied"); + } + + return this.i18nService.t("filterAppliedPlural", numberOfFilters); + }), + ); + + constructor( + private vaultPopupListFiltersService: VaultPopupListFiltersService, + private i18nService: I18nService, + ) {} + + async toggleFilters(isShown: boolean) { + await this.vaultPopupListFiltersService.updateFilterVisibility(isShown); + } +} 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 d9c4fbeee15..56f35c41f6d 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 @@ -1,4 +1,4 @@ -
+
-
- -

- {{ title }} -

- - {{ ciphers.length }} -
-
+ + + + + + + + +
+ +
+ + +
+ + + + +

+ {{ title }} +

+ + + + {{ ciphers.length }} + + + + + +
+
+ +
{{ description }}
+
+ + - +
+ + -
- - -
+ + +
-
+
diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts similarity index 74% rename from apps/browser/src/vault/popup/components/vault/vault-v2.component.ts rename to apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 21c71332997..a0c54987357 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -1,15 +1,17 @@ import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, DestroyRef, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { RouterLink } from "@angular/router"; import { combineLatest, Observable, shareReplay, switchMap } from "rxjs"; +import { filter, map, take } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { ButtonModule, Icons, NoItemsModule } from "@bitwarden/components"; -import { VaultIcons } from "@bitwarden/vault"; +import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components"; +import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; @@ -18,13 +20,15 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; import { VaultUiOnboardingService } from "../../services/vault-ui-onboarding.service"; -import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "../vault-v2"; + +import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component"; import { NewItemDropdownV2Component, NewItemInitialValues, -} from "../vault-v2/new-item-dropdown/new-item-dropdown-v2.component"; -import { VaultListFiltersComponent } from "../vault-v2/vault-list-filters/vault-list-filters.component"; -import { VaultV2SearchComponent } from "../vault-v2/vault-search/vault-v2-search.component"; +} from "./new-item-dropdown/new-item-dropdown-v2.component"; +import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; + +import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; enum VaultState { Empty, @@ -37,6 +41,7 @@ enum VaultState { templateUrl: "vault-v2.component.html", standalone: true, imports: [ + BlockedInjectionBanner, PopupPageComponent, PopupHeaderComponent, PopOutComponent, @@ -46,12 +51,12 @@ enum VaultState { CommonModule, AutofillVaultListItemsComponent, VaultListItemsContainerComponent, - VaultListFiltersComponent, ButtonModule, RouterLink, - VaultV2SearchComponent, NewItemDropdownV2Component, ScrollingModule, + VaultHeaderV2Component, + DecryptionFailureDialogComponent, ], providers: [VaultUiOnboardingService], }) @@ -89,6 +94,9 @@ export class VaultV2Component implements OnInit, OnDestroy { private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, private vaultUiOnboardingService: VaultUiOnboardingService, + private destroyRef: DestroyRef, + private cipherService: CipherService, + private dialogService: DialogService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -116,6 +124,19 @@ export class VaultV2Component implements OnInit, OnDestroy { async ngOnInit() { await this.vaultUiOnboardingService.showOnboardingDialog(); + + this.cipherService.failedToDecryptCiphers$ + .pipe( + map((ciphers) => ciphers.filter((c) => !c.isDeleted)), + filter((ciphers) => ciphers.length > 0), + take(1), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((ciphers) => { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: ciphers.map((c) => c.id as CipherId), + }); + }); } ngOnDestroy(): void {} diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index b173c36d255..7ee15aa833b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -5,6 +5,12 @@ import { Subject } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { + AUTOFILL_ID, + COPY_PASSWORD_ID, + COPY_USERNAME_ID, + COPY_VERIFICATION_CODE_ID, +} from "@bitwarden/common/autofill/constants"; import { EventType } from "@bitwarden/common/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -16,7 +22,10 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { CopyCipherFieldService } from "@bitwarden/vault"; +import { BrowserApi } from "../../../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; @@ -33,15 +42,25 @@ describe("ViewV2Component", () => { const params$ = new Subject(); const mockNavigate = jest.fn(); const collect = jest.fn().mockResolvedValue(null); + const doAutofill = jest.fn().mockResolvedValue(true); + const copy = jest.fn().mockResolvedValue(true); const mockCipher = { id: "122-333-444", type: CipherType.Login, orgId: "222-444-555", + login: { + username: "test-username", + password: "test-password", + totp: "123", + }, }; const mockVaultPopupAutofillService = { - doAutofill: jest.fn(), + doAutofill, + }; + const mockCopyCipherFieldService = { + copy, }; const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -54,6 +73,8 @@ describe("ViewV2Component", () => { beforeEach(async () => { mockNavigate.mockClear(); collect.mockClear(); + doAutofill.mockClear(); + copy.mockClear(); await TestBed.configureTestingModule({ imports: [ViewV2Component], @@ -88,6 +109,10 @@ describe("ViewV2Component", () => { canDeleteCipher$: jest.fn().mockReturnValue(true), }, }, + { + provide: CopyCipherFieldService, + useValue: mockCopyCipherFieldService, + }, ], }).compileComponents(); @@ -112,21 +137,21 @@ describe("ViewV2Component", () => { params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader typelogin"); + expect(component.headerText).toEqual("viewItemHeader typeLogin"); // Set header text for a card mockCipher.type = CipherType.Card; params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader typecard"); + expect(component.headerText).toEqual("viewItemHeader typeCard"); // Set header text for an identity mockCipher.type = CipherType.Identity; params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader typeidentity"); + expect(component.headerText).toEqual("viewItemHeader typeIdentity"); // Set header text for a secure note mockCipher.type = CipherType.SecureNote; @@ -148,5 +173,54 @@ describe("ViewV2Component", () => { undefined, ); })); + + it('invokes `doAutofill` when action="AUTOFILL_ID"', fakeAsync(() => { + params$.next({ action: AUTOFILL_ID }); + + flush(); // Resolve all promises + + expect(doAutofill).toHaveBeenCalledOnce(); + })); + + it('invokes `copy` when action="copy-username"', fakeAsync(() => { + params$.next({ action: COPY_USERNAME_ID }); + + flush(); // Resolve all promises + + expect(copy).toHaveBeenCalledOnce(); + })); + + it('invokes `copy` when action="copy-password"', fakeAsync(() => { + params$.next({ action: COPY_PASSWORD_ID }); + + flush(); // Resolve all promises + + expect(copy).toHaveBeenCalledOnce(); + })); + + it('invokes `copy` when action="copy-totp"', fakeAsync(() => { + params$.next({ action: COPY_VERIFICATION_CODE_ID }); + + flush(); // Resolve all promises + + expect(copy).toHaveBeenCalledOnce(); + })); + + it("closes the popout after a load action", fakeAsync(() => { + jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValueOnce(true); + jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValueOnce(true); + const closeSpy = jest.spyOn(BrowserPopupUtils, "closeSingleActionPopout"); + const focusSpy = jest + .spyOn(BrowserApi, "focusTab") + .mockImplementation(() => Promise.resolve()); + + params$.next({ action: AUTOFILL_ID, senderTabId: 99 }); + + flush(); // Resolve all promises + + expect(doAutofill).toHaveBeenCalledOnce(); + expect(focusSpy).toHaveBeenCalledWith(99); + expect(closeSpy).toHaveBeenCalledOnce(); + })); }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index b1cbe8bc3e4..6532fb004cb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -10,7 +12,13 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AUTOFILL_ID, SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; +import { + AUTOFILL_ID, + COPY_PASSWORD_ID, + COPY_USERNAME_ID, + COPY_VERIFICATION_CODE_ID, + SHOW_AUTOFILL_BUTTON, +} from "@bitwarden/common/autofill/constants"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -18,7 +26,6 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { AsyncActionsModule, @@ -28,19 +35,38 @@ import { SearchModule, ToastService, } from "@bitwarden/components"; +import { CopyCipherFieldService } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PremiumUpgradePromptService } from "../../../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view"; +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 { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service"; import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service"; +import { closeViewVaultItemPopout, VaultPopoutType } from "../../../utils/vault-popout-window"; 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"; import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; +/** + * The types of actions that can be triggered when loading the view vault item popout via the + * extension ContextMenu. See context-menu-clicked-handler.ts for more information. + */ +type LoadAction = + | typeof AUTOFILL_ID + | typeof SHOW_AUTOFILL_BUTTON + | typeof COPY_USERNAME_ID + | typeof COPY_PASSWORD_ID + | typeof COPY_VERIFICATION_CODE_ID; + @Component({ selector: "app-view-v2", templateUrl: "view-v2.component.html", @@ -68,10 +94,10 @@ export class ViewV2Component { headerText: string; cipher: CipherView; organization$: Observable; - folder$: Observable; canDeleteCipher$: Observable; collections$: Observable; - loadAction: typeof AUTOFILL_ID | typeof SHOW_AUTOFILL_BUTTON; + loadAction: LoadAction; + senderTabId?: number; constructor( private route: ActivatedRoute, @@ -86,6 +112,7 @@ export class ViewV2Component { private eventCollectionService: EventCollectionService, private popupRouterCacheService: PopupRouterCacheService, protected cipherAuthorizationService: CipherAuthorizationService, + private copyCipherFieldService: CopyCipherFieldService, ) { this.subscribeToParams(); } @@ -95,13 +122,15 @@ export class ViewV2Component { .pipe( switchMap(async (params): Promise => { this.loadAction = params.action; + this.senderTabId = params.senderTabId ? parseInt(params.senderTabId, 10) : undefined; return await this.getCipherData(params.cipherId); }), switchMap(async (cipher) => { this.cipher = cipher; this.headerText = this.setHeader(cipher.type); - if (this.loadAction === AUTOFILL_ID || this.loadAction === SHOW_AUTOFILL_BUTTON) { - await this.vaultPopupAutofillService.doAutofill(this.cipher); + + if (this.loadAction) { + await this._handleLoadAction(this.loadAction, this.senderTabId); } this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(cipher); @@ -121,18 +150,15 @@ export class ViewV2Component { setHeader(type: CipherType) { switch (type) { case CipherType.Login: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeLogin").toLowerCase()); + return this.i18nService.t("viewItemHeader", this.i18nService.t("typeLogin")); case CipherType.Card: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeCard").toLowerCase()); + return this.i18nService.t("viewItemHeader", this.i18nService.t("typeCard")); case CipherType.Identity: - return this.i18nService.t( - "viewItemHeader", - this.i18nService.t("typeIdentity").toLowerCase(), - ); + return this.i18nService.t("viewItemHeader", this.i18nService.t("typeIdentity")); case CipherType.SecureNote: - return this.i18nService.t("viewItemHeader", this.i18nService.t("note").toLowerCase()); + return this.i18nService.t("viewItemHeader", this.i18nService.t("note")); case CipherType.SshKey: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeSshkey").toLowerCase()); + return this.i18nService.t("viewItemHeader", this.i18nService.t("typeSshkey")); } } @@ -211,4 +237,65 @@ export class ViewV2Component { protected showFooter(): boolean { return this.cipher && (!this.cipher.isDeleted || (this.cipher.isDeleted && this.cipher.edit)); } + + /** + * Handles the load action for the view vault item popout. These actions are typically triggered + * via the extension context menu. It is necessary to render the view for items that have password + * reprompt enabled. + * @param loadAction + * @param senderTabId + * @private + */ + private async _handleLoadAction(loadAction: LoadAction, senderTabId?: number): Promise { + let actionSuccess = false; + + // Both vaultPopupAutofillService and copyCipherFieldService will perform password re-prompting internally. + + switch (loadAction) { + case "show-autofill-button": + // This action simply shows the cipher view, no need to do anything. + return; + case "autofill": + actionSuccess = await this.vaultPopupAutofillService.doAutofill(this.cipher, false); + break; + case "copy-username": + actionSuccess = await this.copyCipherFieldService.copy( + this.cipher.login.username, + "username", + this.cipher, + ); + break; + case "copy-password": + actionSuccess = await this.copyCipherFieldService.copy( + this.cipher.login.password, + "password", + this.cipher, + ); + break; + case "copy-totp": + actionSuccess = await this.copyCipherFieldService.copy( + this.cipher.login.totp, + "totp", + this.cipher, + ); + break; + } + + if (BrowserPopupUtils.inPopout(window)) { + setTimeout( + async () => { + if ( + BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.viewVaultItem) && + senderTabId + ) { + await BrowserApi.focusTab(senderTabId); + await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`); + } else { + await this.popupRouterCacheService.back(); + } + }, + actionSuccess ? 1000 : 0, + ); + } + } } diff --git a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html b/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html deleted file mode 100644 index 8464655c20b..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html +++ /dev/null @@ -1,140 +0,0 @@ -
-

- {{ "customFields" | i18n }} -

-
- -
-
- - - -
- - - - - - - -
- - -
- -
-
- -
-
-
- -
- - - -
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts deleted file mode 100644 index 6992455a8a6..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from "@angular/core"; - -import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/vault/components/add-edit-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -@Component({ - selector: "app-vault-add-edit-custom-fields", - templateUrl: "add-edit-custom-fields.component.html", -}) -export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { - constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { - super(i18nService, eventCollectionService); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.html b/apps/browser/src/vault/popup/components/vault/add-edit.component.html deleted file mode 100644 index fb1efbbbd79..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.html +++ /dev/null @@ -1,826 +0,0 @@ - -
-
- -
-

- {{ title }} -

-
- -
-
-
- - {{ "personalOwnershipPolicyInEffect" | i18n }} - -
-

- {{ "itemInformation" | i18n }} -

-
-
- - -
-
- - -
- -
-
-
- - -
-
- -
-
-
-
- - -
-
- - - -
-
- - -
-
-
- -
- {{ "typePasskey" | i18n }} - {{ "dateCreated" | i18n }} - {{ cipher.login.fido2Credentials[0].creationDate | date: "short" }} -
-
-
-
- -
-
- - -
-
- - - -
-
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
- - - - - - - -
-
- - - - - - - -
-
- - -
-
-
- - -
-
- -
-
-
- -
-
- - - - - - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- {{ "sshPrivateKey" | i18n }} - {{ cipher.sshKey.privateKey }} -
-
- {{ "sshPublicKey" | i18n }} - {{ cipher.sshKey.publicKey }} -
-
- {{ "sshKeyFingerprint" | i18n }} - {{ cipher.sshKey.keyFingerprint }} -
-
-
-
-
-
- -
- -
- - - - - - -
-
- - -
-
-
- -
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- - -
-
- - -
- - -
-
-
-

- -

-
-
- -
-
-
- - -
-

- {{ "ownership" | i18n }} -

-
-
- - -
-
-
-
-

- {{ "collections" | i18n }} -

-
-
- {{ "noCollectionsInList" | i18n }} -
-
-
-
- - -
-
-
-
-
- -
-
-
- diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts deleted file mode 100644 index 94654e41541..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ /dev/null @@ -1,418 +0,0 @@ -import { DatePipe, Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import qrcodeParser from "qrcode-parser"; -import { firstValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; -import { PasswordRepromptService } 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 { PopupCloseWarningService } from "../../../../popup/services/popup-close-warning.service"; -import { Fido2UserVerificationService } from "../../../services/fido2-user-verification.service"; -import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; -import { closeAddEditVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; - -@Component({ - selector: "app-vault-add-edit", - templateUrl: "add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AddEditComponent extends BaseAddEditComponent implements OnInit { - currentUris: string[]; - showAttachments = true; - openAttachmentsInPopup: boolean; - showAutoFillOnPageLoadOptions: boolean; - - private fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - cipherService: CipherService, - folderService: FolderService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - accountService: AccountService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - collectionService: CollectionService, - messagingService: MessagingService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - private popupCloseWarningService: PopupCloseWarningService, - organizationService: OrganizationService, - passwordRepromptService: PasswordRepromptService, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - datePipe: DatePipe, - configService: ConfigService, - private fido2UserVerificationService: Fido2UserVerificationService, - cipherAuthorizationService: CipherAuthorizationService, - ) { - super( - cipherService, - folderService, - i18nService, - platformUtilsService, - auditService, - accountService, - collectionService, - messagingService, - eventCollectionService, - policyService, - logService, - passwordRepromptService, - organizationService, - sendApiService, - dialogService, - window, - datePipe, - configService, - cipherAuthorizationService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } - if (params.folderId) { - this.folderId = params.folderId; - } - if (params.collectionId) { - this.collectionId = params.collectionId; - const collection = this.writeableCollections.find((c) => c.id === params.collectionId); - if (collection != null) { - this.collectionIds = [collection.id]; - this.organizationId = collection.organizationId; - } - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - this.editMode = !params.cipherId; - - if (params.cloneMode != null) { - this.cloneMode = params.cloneMode === "true"; - } - if (params.selectedVault) { - this.organizationId = params.selectedVault; - } - - await this.load(); - - if (!this.editMode || this.cloneMode) { - // Only allow setting username if there's no existing value - if ( - params.username && - (this.cipher.login.username == null || this.cipher.login.username === "") - ) { - this.cipher.login.username = params.username; - } - - if (params.name && (this.cipher.name == null || this.cipher.name === "")) { - this.cipher.name = params.name; - } - if ( - params.uri && - this.cipher.login.uris[0] && - (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "") - ) { - this.cipher.login.uris[0].uri = params.uri; - } - } - - this.openAttachmentsInPopup = BrowserPopupUtils.inPopup(window); - - if (this.inAddEditPopoutWindow()) { - BrowserApi.messageListener("add-edit-popout", this.handleExtensionMessage.bind(this)); - } - }); - - if (!this.editMode) { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - this.currentUris = - tabs == null - ? null - : tabs.filter((tab) => tab.url != null && tab.url !== "").map((tab) => tab.url); - } - - this.setFocus(); - - if (BrowserPopupUtils.inPopout(window)) { - this.popupCloseWarningService.enable(); - } - } - - async load() { - await super.load(); - this.showAutoFillOnPageLoadOptions = - this.cipher.type === CipherType.Login && - (await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)); - } - - async submit(): Promise { - const fido2SessionData = await firstValueFrom(this.fido2PopoutSessionData$); - const { isFido2Session, sessionId, userVerification } = fido2SessionData; - const inFido2PopoutWindow = BrowserPopupUtils.inPopout(window) && isFido2Session; - - // normalize card expiry year on save - if (this.cipher.type === this.cipherType.Card) { - this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); - } - - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - if ( - inFido2PopoutWindow && - !(await this.handleFido2UserVerification(sessionId, userVerification)) - ) { - return false; - } - - const success = await super.submit(); - if (!success) { - return false; - } - - if (BrowserPopupUtils.inPopout(window)) { - this.popupCloseWarningService.disable(); - } - - if (inFido2PopoutWindow) { - BrowserFido2UserInterfaceSession.confirmNewCredentialResponse( - sessionId, - this.cipher.id, - userVerification, - ); - return true; - } - - if (this.inAddEditPopoutWindow()) { - this.messagingService.send("addEditCipherSubmitted"); - await closeAddEditVaultItemPopout(1000); - return true; - } - - if (this.cloneMode) { - // 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(["/tabs/vault"]); - } else { - this.location.back(); - } - return true; - } - - attachments() { - super.attachments(); - - if (this.openAttachmentsInPopup) { - const destinationUrl = this.router - .createUrlTree(["/attachments"], { queryParams: { cipherId: this.cipher.id } }) - .toString(); - const currentBaseUrl = window.location.href.replace(this.router.url, ""); - // 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 - BrowserPopupUtils.openCurrentPagePopout(window, currentBaseUrl + destinationUrl); - } else { - // 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(["/attachments"], { queryParams: { cipherId: this.cipher.id } }); - } - } - - editCollections() { - super.editCollections(); - if (this.cipher.organizationId != null) { - // 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(["/collections"], { queryParams: { cipherId: this.cipher.id } }); - } - } - - async cancel() { - super.cancel(); - - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - if (BrowserPopupUtils.inPopout(window) && sessionData.isFido2Session) { - this.popupCloseWarningService.disable(); - BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId); - return; - } - - if (this.inAddEditPopoutWindow()) { - // 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 - closeAddEditVaultItemPopout(); - return; - } - - this.location.back(); - } - - async generateUsername(): Promise { - const confirmed = await super.generateUsername(); - if (confirmed) { - await this.saveCipherState(); - // 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(["generator"], { queryParams: { type: "username" } }); - } - return confirmed; - } - - async generatePassword(): Promise { - const confirmed = await super.generatePassword(); - if (confirmed) { - await this.saveCipherState(); - // 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(["generator"], { queryParams: { type: "password" } }); - } - return confirmed; - } - - async delete(): Promise { - const confirmed = await super.delete(); - if (confirmed) { - // 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(["/tabs/vault"]); - } - return confirmed; - } - - toggleUriInput(uri: LoginUriView) { - const u = uri as any; - u.showCurrentUris = !u.showCurrentUris; - } - - allowOwnershipOptions(): boolean { - return ( - (!this.editMode || this.cloneMode) && - this.ownershipOptions && - (this.ownershipOptions.length > 1 || !this.allowPersonal) - ); - } - - private saveCipherState() { - return this.cipherService.setAddEditCipherInfo({ - cipher: this.cipher, - collectionIds: - this.collections == null - ? [] - : this.collections.filter((c) => (c as any).checked).map((c) => c.id), - }); - } - - private setFocus() { - window.setTimeout(() => { - if (this.editMode) { - return; - } - - if (this.cipher.name != null && this.cipher.name !== "") { - document.getElementById("loginUsername").focus(); - } else { - document.getElementById("name").focus(); - } - }, 200); - } - - repromptChanged() { - super.repromptChanged(); - - if (!this.showAutoFillOnPageLoadOptions) { - return; - } - - if (this.reprompt) { - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("passwordRepromptDisabledAutofillOnPageLoad"), - ); - return; - } - - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("autofillOnPageLoadSetToDefault"), - ); - } - - private inAddEditPopoutWindow() { - return BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.addEditVaultItem); - } - - async captureTOTPFromTab() { - try { - const screenshot = await BrowserApi.captureVisibleTab(); - const data = await qrcodeParser(screenshot); - const url = new URL(data.toString()); - if (url.protocol == "otpauth:" && url.searchParams.has("secret")) { - this.cipher.login.totp = data.toString(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("totpCaptureSuccess"), - ); - } - } catch (e) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("totpCaptureError"), - ); - } - } - - private handleExtensionMessage(message: { [key: string]: any; command: string }) { - if (message.command === "inlineAutofillMenuRefreshAddEditCipher") { - this.load().catch((error) => this.logService.error(error)); - } - } - - // TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production. - // Be sure to make the same changes to add-edit-v2.component.ts if applicable - private async handleFido2UserVerification( - sessionId: string, - userVerification: boolean, - ): Promise { - // We are bypassing user verification pending approval for production. - return true; - } -} diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.html b/apps/browser/src/vault/popup/components/vault/attachments.component.html deleted file mode 100644 index b95dc69af8f..00000000000 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.html +++ /dev/null @@ -1,72 +0,0 @@ -
-
-
- - -
-

- {{ "attachments" | i18n }} -

-
- -
-
-
-
-
-
-
- {{ a.fileName }} -
- {{ a.sizeName }} -
- -
-
-
-
-
-

- {{ "newAttachment" | i18n }} -

-
-
- - -
-
- -
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.ts b/apps/browser/src/vault/popup/components/vault/attachments.component.ts deleted file mode 100644 index 346451a8af0..00000000000 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; -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/platform/abstractions/encrypt.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"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-vault-attachments", - templateUrl: "attachments.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AttachmentsComponent extends BaseAttachmentsComponent implements OnInit { - openedAttachmentsInPopup: boolean; - - constructor( - cipherService: CipherService, - i18nService: I18nService, - keyService: KeyService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - apiService: ApiService, - private location: Location, - private route: ActivatedRoute, - stateService: StateService, - logService: LogService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - billingAccountProfileStateService: BillingAccountProfileStateService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - cipherService, - i18nService, - keyService, - encryptService, - platformUtilsService, - apiService, - window, - logService, - stateService, - fileDownloadService, - dialogService, - billingAccountProfileStateService, - accountService, - toastService, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.cipherId = params.cipherId; - await this.init(); - }); - - this.openedAttachmentsInPopup = history.length === 1; - } - - back() { - this.location.back(); - } - - close() { - window.close(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.html b/apps/browser/src/vault/popup/components/vault/collections.component.html deleted file mode 100644 index 36c1336c5b4..00000000000 --- a/apps/browser/src/vault/popup/components/vault/collections.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
- -
-

- {{ "collections" | i18n }} -

-
- -
-
-
-
-
-
- {{ "noCollectionsInList" | i18n }} -
-
-
-
- - -
-
-
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.ts b/apps/browser/src/vault/popup/components/vault/collections.component.ts deleted file mode 100644 index 407f87e996c..00000000000 --- a/apps/browser/src/vault/popup/components/vault/collections.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { ToastService } from "@bitwarden/components"; - -@Component({ - selector: "app-vault-collections", - templateUrl: "collections.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class CollectionsComponent extends BaseCollectionsComponent implements OnInit { - constructor( - collectionService: CollectionService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - cipherService: CipherService, - organizationService: OrganizationService, - private route: ActivatedRoute, - private location: Location, - logService: LogService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - collectionService, - platformUtilsService, - i18nService, - cipherService, - organizationService, - logService, - accountService, - toastService, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.onSavedCollections.subscribe(() => { - this.back(); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.cipherId = params.cipherId; - await this.load(); - }); - } - - back() { - this.location.back(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.html b/apps/browser/src/vault/popup/components/vault/current-tab.component.html deleted file mode 100644 index bb8a401da62..00000000000 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.html +++ /dev/null @@ -1,95 +0,0 @@ - -

{{ "currentTab" | i18n }}

-
- - -
- -
- -
-
-
-
- -
- - -
-

- {{ "typeLogins" | i18n }} - {{ loginCiphers.length }} -

-
- - -
-

{{ "autoFillInfo" | i18n }}

- -
-
-
-
-

- {{ "cards" | i18n }} - {{ cardCiphers.length }} -

-
- -
-
-
-

- {{ "identities" | i18n }} - {{ identityCiphers.length }} -

-
- -
-
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts deleted file mode 100644 index ec69330745f..00000000000 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { Subject, firstValueFrom, from, Subscription } from "rxjs"; -import { debounceTime, switchMap, takeUntil } from "rxjs/operators"; - -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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 { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const BroadcasterSubscriptionId = "CurrentTabComponent"; - -@Component({ - selector: "app-current-tab", - templateUrl: "current-tab.component.html", -}) -export class CurrentTabComponent implements OnInit, OnDestroy { - pageDetails: any[] = []; - tab: chrome.tabs.Tab; - cardCiphers: CipherView[]; - identityCiphers: CipherView[]; - loginCiphers: CipherView[]; - url: string; - hostname: string; - searchText: string; - inSidebar = false; - searchTypeSearch = false; - loaded = false; - isLoading = false; - showOrganizations = false; - showHowToAutofill = false; - autofillCalloutText: string; - protected search$ = new Subject(); - private destroy$ = new Subject(); - private collectPageDetailsSubscription: Subscription; - - private totpCode: string; - private totpTimeout: number; - private loadedTimeout: number; - private searchTimeout: number; - - constructor( - private platformUtilsService: PlatformUtilsService, - private cipherService: CipherService, - private autofillService: AutofillService, - private i18nService: I18nService, - private router: Router, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private syncService: SyncService, - private searchService: SearchService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, - private vaultFilterService: VaultFilterService, - private vaultSettingsService: VaultSettingsService, - ) {} - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.inSidebar = BrowserPopupUtils.inSidebar(window); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (this.isLoading) { - window.setTimeout(() => { - // 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.load(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - if (!this.syncService.syncInProgress) { - await this.load(); - await this.setCallout(); - } else { - this.loadedTimeout = window.setTimeout(async () => { - if (!this.isLoading) { - await this.load(); - await this.setCallout(); - } - }, 5000); - } - - this.search$ - .pipe( - debounceTime(500), - switchMap(() => { - return from(this.searchVault()); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - const autofillOnPageLoadOrgPolicy = await firstValueFrom( - this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$, - ); - const autofillOnPageLoadPolicyToastHasDisplayed = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoadPolicyToastHasDisplayed$, - ); - - // If the org "autofill on page load" policy is set, set the user setting to match it - // @TODO override user setting instead of overwriting - if (autofillOnPageLoadOrgPolicy === true) { - await this.autofillSettingsService.setAutofillOnPageLoad(true); - - if (!autofillOnPageLoadPolicyToastHasDisplayed) { - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("autofillPageLoadPolicyActivated"), - ); - - await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(true); - } - } - - // If the org policy is ever disabled after being enabled, reset the toast notification - if (!autofillOnPageLoadOrgPolicy && autofillOnPageLoadPolicyToastHasDisplayed) { - await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(false); - } - } - - ngOnDestroy() { - window.clearTimeout(this.loadedTimeout); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - - this.destroy$.next(); - this.destroy$.complete(); - } - - async refresh() { - await this.load(); - } - - addCipher() { - // 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(["/add-cipher"], { - queryParams: { - name: this.hostname, - uri: this.url, - selectedVault: this.vaultFilterService.getVaultFilter().selectedOrganizationId, - }, - }); - } - - viewCipher(cipher: CipherView) { - // 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(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); - } - - async fillCipher(cipher: CipherView, closePopupDelay?: number) { - if ( - cipher.reprompt !== CipherRepromptType.None && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - return; - } - - this.totpCode = null; - if (this.totpTimeout != null) { - window.clearTimeout(this.totpTimeout); - } - - if (this.pageDetails == null || this.pageDetails.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - return; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - tab: this.tab, - cipher: cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - allowTotpAutofill: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - if (BrowserPopupUtils.inPopup(window)) { - if (!closePopupDelay) { - if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) { - BrowserApi.closePopup(window); - } else { - // Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard - setTimeout(() => BrowserApi.closePopup(window), 50); - } - } else { - setTimeout(() => BrowserApi.closePopup(window), closePopupDelay); - } - } - } catch { - this.ngZone.run(() => { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - this.changeDetectorRef.detectChanges(); - }); - } - } - - async searchVault() { - if (!(await this.searchService.isSearchable(this.searchText))) { - return; - } - - await this.router.navigate(["/tabs/vault"], { queryParams: { searchText: this.searchText } }); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { - BrowserApi.closePopup(window); - } - } - - protected async load() { - this.isLoading = false; - this.tab = await BrowserApi.getTabFromCurrentWindow(); - - if (this.tab != null) { - this.url = this.tab.url; - } else { - this.loginCiphers = []; - this.isLoading = this.loaded = true; - return; - } - - this.pageDetails = []; - this.collectPageDetailsSubscription?.unsubscribe(); - this.collectPageDetailsSubscription = this.autofillService - .collectPageDetailsFromTab$(this.tab) - .pipe(takeUntil(this.destroy$)) - .subscribe((pageDetails) => (this.pageDetails = pageDetails)); - - this.hostname = Utils.getHostname(this.url); - const otherTypes: CipherType[] = []; - const dontShowCards = !(await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$)); - const dontShowIdentities = !(await firstValueFrom( - this.vaultSettingsService.showIdentitiesCurrentTab$, - )); - this.showOrganizations = await this.organizationService.hasOrganizations(); - if (!dontShowCards) { - otherTypes.push(CipherType.Card); - } - if (!dontShowIdentities) { - otherTypes.push(CipherType.Identity); - } - - const ciphers = await this.cipherService.getAllDecryptedForUrl( - this.url, - otherTypes.length > 0 ? otherTypes : null, - ); - - this.loginCiphers = []; - this.cardCiphers = []; - this.identityCiphers = []; - - ciphers.forEach((c) => { - if (!this.vaultFilterService.filterCipherForSelectedVault(c)) { - switch (c.type) { - case CipherType.Login: - this.loginCiphers.push(c); - break; - case CipherType.Card: - this.cardCiphers.push(c); - break; - case CipherType.Identity: - this.identityCiphers.push(c); - break; - default: - break; - } - } - }); - - if (this.loginCiphers.length) { - this.loginCiphers = this.loginCiphers.sort((a, b) => - this.cipherService.sortCiphersByLastUsedThenName(a, b), - ); - } - - this.isLoading = this.loaded = true; - } - - async goToSettings() { - // 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(["autofill"]); - } - - async dismissCallout() { - await this.autofillSettingsService.setAutofillOnPageLoadCalloutIsDismissed(true); - this.showHowToAutofill = false; - } - - private async setCallout() { - const inlineMenuVisibilityIsOff = - (await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$)) === - AutofillOverlayVisibility.Off; - - this.showHowToAutofill = - this.loginCiphers.length > 0 && - inlineMenuVisibilityIsOff && - !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)) && - !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoadCalloutIsDismissed$)); - - if (this.showHowToAutofill) { - const autofillCommand = await this.platformUtilsService.getAutofillKeyboardShortcut(); - await this.setAutofillCalloutText(autofillCommand); - } - } - - private setAutofillCalloutText(command: string) { - if (command) { - this.autofillCalloutText = this.i18nService.t("autofillSelectInfoWithCommand", command); - } else { - this.autofillCalloutText = this.i18nService.t("autofillSelectInfoWithoutCommand"); - } - } -} diff --git a/apps/browser/src/vault/popup/components/vault/password-history.component.html b/apps/browser/src/vault/popup/components/vault/password-history.component.html deleted file mode 100644 index 6286aa1022d..00000000000 --- a/apps/browser/src/vault/popup/components/vault/password-history.component.html +++ /dev/null @@ -1,40 +0,0 @@ -
-
- -
-

- {{ "passwordHistory" | i18n }} -

-
-
-
-
-
-
-
-
- - {{ h.lastUsedDate | date: "medium" }} -
-
-
- -
-
-
-
-
-

{{ "noPasswordsInList" | i18n }}

-
-
diff --git a/apps/browser/src/vault/popup/components/vault/password-history.component.ts b/apps/browser/src/vault/popup/components/vault/password-history.component.ts deleted file mode 100644 index bf1b4ea7717..00000000000 --- a/apps/browser/src/vault/popup/components/vault/password-history.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.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"; - -@Component({ - selector: "app-password-history", - templateUrl: "password-history.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class PasswordHistoryComponent extends BasePasswordHistoryComponent implements OnInit { - constructor( - cipherService: CipherService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - accountService: AccountService, - private location: Location, - private route: ActivatedRoute, - ) { - super(cipherService, platformUtilsService, i18nService, accountService, window); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } else { - this.close(); - } - await this.init(); - }); - } - - close() { - this.location.back(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/share.component.html b/apps/browser/src/vault/popup/components/vault/share.component.html deleted file mode 100644 index 46aaecd06b8..00000000000 --- a/apps/browser/src/vault/popup/components/vault/share.component.html +++ /dev/null @@ -1,77 +0,0 @@ -
- -
-
- -
-

- {{ "moveToOrganization" | i18n }} -

-
- -
-
-
-
-
-
- {{ "noOrganizationsList" | i18n }} -
-
-
-
- - -
-
- -
-
-

- {{ "collections" | i18n }} -

-
-
- {{ "noCollectionsInList" | i18n }} -
-
-
-
- - -
-
-
-
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/share.component.ts b/apps/browser/src/vault/popup/components/vault/share.component.ts deleted file mode 100644 index 8e061665b73..00000000000 --- a/apps/browser/src/vault/popup/components/vault/share.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; - -@Component({ - selector: "app-vault-share", - templateUrl: "share.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class ShareComponent extends BaseShareComponent implements OnInit { - constructor( - collectionService: CollectionService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - logService: LogService, - cipherService: CipherService, - private route: ActivatedRoute, - private router: Router, - organizationService: OrganizationService, - accountService: AccountService, - ) { - super( - collectionService, - platformUtilsService, - i18nService, - cipherService, - logService, - organizationService, - accountService, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.onSharedCipher.subscribe(() => { - // 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(["view-cipher", { cipherId: this.cipherId }]); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.cipherId = params.cipherId; - await this.load(); - }); - } - - async submit(): Promise { - const success = await super.submit(); - if (success) { - this.cancel(); - } - return success; - } - - cancel() { - // 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(["/view-cipher"], { - replaceUrl: true, - queryParams: { cipherId: this.cipher.id }, - }); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html b/apps/browser/src/vault/popup/components/vault/vault-filter.component.html deleted file mode 100644 index f5c28b2bebd..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html +++ /dev/null @@ -1,237 +0,0 @@ - -
- -
-

{{ "myVault" | i18n }}

- -
- -
-
-
- -
- - - -

{{ "noItemsInList" | i18n }}

- -
-
- -
-

- {{ "favorites" | i18n }} - {{ favoriteCiphers.length }} -

-
- - -
-
-
-

- {{ "types" | i18n }} - 4 -

-
- - - - - -
-
-
-

- {{ "folders" | i18n }} - {{ folderCount }} -

-
- -
-
-
-

- {{ "collections" | i18n }} - {{ nestedCollections.length }} -

-
- -
-
-
-

- {{ "noneFolder" | i18n }} -
{{ noFolderCiphers.length }}
-

-
- - -
-
-
-

- {{ "trash" | i18n }} - {{ deletedCount }} -

-
- -
-
-
- -
-

{{ "noItemsInList" | i18n }}

-
- -
-
- - -
-
-
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts deleted file mode 100644 index 448f85a8cbd..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ /dev/null @@ -1,473 +0,0 @@ -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; -import { first, switchMap, takeUntil } from "rxjs/operators"; - -import { CollectionView } from "@bitwarden/admin-console/common"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserGroupingsComponentState } from "../../../../models/browserGroupingsComponentState"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const ComponentId = "VaultComponent"; - -@Component({ - selector: "app-vault-filter", - templateUrl: "vault-filter.component.html", -}) -export class VaultFilterComponent implements OnInit, OnDestroy { - get showNoFolderCiphers(): boolean { - return ( - this.noFolderCiphers != null && - this.noFolderCiphers.length < this.noFolderListSize && - this.collections.length === 0 - ); - } - - get folderCount(): number { - return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1); - } - folders: FolderView[]; - nestedFolders: TreeNode[]; - collections: CollectionView[]; - nestedCollections: TreeNode[]; - loaded = false; - cipherType = CipherType; - ciphers: CipherView[]; - favoriteCiphers: CipherView[]; - noFolderCiphers: CipherView[]; - folderCounts = new Map(); - collectionCounts = new Map(); - typeCounts = new Map(); - state: BrowserGroupingsComponentState; - showLeftHeader = true; - searchPending = false; - searchTypeSearch = false; - deletedCount = 0; - vaultFilter: VaultFilter; - selectedOrganization: string = null; - showCollections = true; - - private loadedTimeout: number; - private selectedTimeout: number; - private preventSelected = false; - private noFolderListSize = 100; - private searchTimeout: any = null; - private hasSearched = false; - private hasLoadedAllCiphers = false; - private allCiphers: CipherView[] = null; - private destroy$ = new Subject(); - private _searchText$ = new BehaviorSubject(""); - private isSearchable: boolean = false; - - get searchText() { - return this._searchText$.value; - } - set searchText(value: string) { - this._searchText$.next(value); - } - - constructor( - private i18nService: I18nService, - private cipherService: CipherService, - private router: Router, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private route: ActivatedRoute, - private syncService: SyncService, - private platformUtilsService: PlatformUtilsService, - private searchService: SearchService, - private location: Location, - private vaultFilterService: VaultFilterService, - private vaultBrowserStateService: VaultBrowserStateService, - ) { - this.noFolderListSize = 100; - } - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showLeftHeader = !( - BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() - ); - await this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - window.setTimeout(() => { - // 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.load(); - }, 500); - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - const restoredScopeState = await this.restoreState(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); - if (this.state?.searchText) { - this.searchText = this.state.searchText; - } else if (params.searchText) { - this.searchText = params.searchText; - this.location.replaceState("vault"); - } - - if (!this.syncService.syncInProgress) { - // 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.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - // 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.load(); - } - }, 5000); - } - - if (!this.syncService.syncInProgress || restoredScopeState) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY); - } - }); - - this._searchText$ - .pipe( - switchMap((searchText) => from(this.searchService.isSearchable(searchText))), - takeUntil(this.destroy$), - ) - .subscribe((isSearchable) => { - this.isSearchable = isSearchable; - }); - } - - ngOnDestroy() { - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); - } - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - // 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.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - this.destroy$.next(); - this.destroy$.complete(); - } - - async load() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - - this.updateSelectedOrg(); - await this.loadCollectionsAndFolders(); - await this.loadCiphers(); - - if (this.showNoFolderCiphers && this.nestedFolders.length > 0) { - // Remove "No Folder" from folder listing - this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1); - } - - this.loaded = true; - } - - async loadCiphers() { - this.allCiphers = await this.cipherService.getAllDecrypted(); - if (!this.hasLoadedAllCiphers) { - this.hasLoadedAllCiphers = !(await this.searchService.isSearchable(this.searchText)); - } - await this.search(null); - this.getCounts(); - } - - async loadCollections() { - const allCollections = await this.vaultFilterService.buildCollections( - this.selectedOrganization, - ); - this.collections = allCollections.fullList; - this.nestedCollections = allCollections.nestedList; - } - - async loadFolders() { - const allFolders = await firstValueFrom( - this.vaultFilterService.buildNestedFolders(this.selectedOrganization), - ); - this.folders = allFolders.fullList; - this.nestedFolders = allFolders.nestedList; - } - - async search(timeout: number = null) { - this.searchPending = false; - if (this.searchTimeout != null) { - clearTimeout(this.searchTimeout); - } - const filterDeleted = (c: CipherView) => !c.isDeleted; - if (timeout == null) { - this.hasSearched = this.isSearchable; - this.ciphers = await this.searchService.searchCiphers( - this.searchText, - filterDeleted, - this.allCiphers, - ); - this.ciphers = this.ciphers.filter( - (c) => !this.vaultFilterService.filterCipherForSelectedVault(c), - ); - return; - } - this.searchPending = true; - this.searchTimeout = setTimeout(async () => { - this.hasSearched = this.isSearchable; - if (!this.hasLoadedAllCiphers && !this.hasSearched) { - await this.loadCiphers(); - } else { - this.ciphers = await this.searchService.searchCiphers( - this.searchText, - filterDeleted, - this.allCiphers, - ); - } - this.ciphers = this.ciphers.filter( - (c) => !this.vaultFilterService.filterCipherForSelectedVault(c), - ); - this.searchPending = false; - }, timeout); - } - - async selectType(type: CipherType) { - // 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(["/ciphers"], { queryParams: { type: type } }); - } - - async selectFolder(folder: FolderView) { - // 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(["/ciphers"], { queryParams: { folderId: folder.id || "none" } }); - } - - async selectCollection(collection: CollectionView) { - // 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(["/ciphers"], { queryParams: { collectionId: collection.id } }); - } - - async selectTrash() { - // 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(["/ciphers"], { queryParams: { deleted: true } }); - } - - async selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - // 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(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); - } - this.preventSelected = false; - }, 200); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); - // 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 - BrowserApi.createNewTab(cipher.login.launchUri); - if (BrowserPopupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - async addCipher() { - // 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(["/add-cipher"], { - queryParams: { selectedVault: this.vaultFilter.selectedOrganizationId }, - }); - } - - async vaultFilterChanged() { - if (this.showSearching) { - await this.search(); - } - this.updateSelectedOrg(); - await this.loadCollectionsAndFolders(); - this.getCounts(); - } - - updateSelectedOrg() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - if (this.vaultFilter.selectedOrganizationId != null) { - this.selectedOrganization = this.vaultFilter.selectedOrganizationId; - } else { - this.selectedOrganization = null; - } - } - - getCounts() { - let favoriteCiphers: CipherView[] = null; - let noFolderCiphers: CipherView[] = null; - const folderCounts = new Map(); - const collectionCounts = new Map(); - const typeCounts = new Map(); - - this.deletedCount = this.allCiphers.filter( - (c) => c.isDeleted && !this.vaultFilterService.filterCipherForSelectedVault(c), - ).length; - - this.ciphers?.forEach((c) => { - if (!this.vaultFilterService.filterCipherForSelectedVault(c)) { - if (c.isDeleted) { - return; - } - if (c.favorite) { - if (favoriteCiphers == null) { - favoriteCiphers = []; - } - favoriteCiphers.push(c); - } - - if (c.folderId == null) { - if (noFolderCiphers == null) { - noFolderCiphers = []; - } - noFolderCiphers.push(c); - } - - if (typeCounts.has(c.type)) { - typeCounts.set(c.type, typeCounts.get(c.type) + 1); - } else { - typeCounts.set(c.type, 1); - } - - if (folderCounts.has(c.folderId)) { - folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1); - } else { - folderCounts.set(c.folderId, 1); - } - - if (c.collectionIds != null) { - c.collectionIds.forEach((colId) => { - if (collectionCounts.has(colId)) { - collectionCounts.set(colId, collectionCounts.get(colId) + 1); - } else { - collectionCounts.set(colId, 1); - } - }); - } - } - }); - - this.favoriteCiphers = favoriteCiphers; - this.noFolderCiphers = noFolderCiphers; - this.typeCounts = typeCounts; - this.folderCounts = folderCounts; - this.collectionCounts = collectionCounts; - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.isSearchable); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { - BrowserApi.closePopup(window); - } - } - - private async loadCollectionsAndFolders() { - this.showCollections = !this.vaultFilter.myVaultOnly; - await this.loadFolders(); - await this.loadCollections(); - } - - private async saveState() { - this.state = Object.assign(new BrowserGroupingsComponentState(), { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - favoriteCiphers: this.favoriteCiphers, - noFolderCiphers: this.noFolderCiphers, - ciphers: this.ciphers, - collectionCounts: this.collectionCounts, - folderCounts: this.folderCounts, - typeCounts: this.typeCounts, - folders: this.folders, - collections: this.collections, - deletedCount: this.deletedCount, - }); - await this.vaultBrowserStateService.setBrowserGroupingsComponentState(this.state); - } - - private async restoreState(): Promise { - this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); - if (this.state == null) { - return false; - } - - if (this.state.favoriteCiphers != null) { - this.favoriteCiphers = this.state.favoriteCiphers; - } - if (this.state.noFolderCiphers != null) { - this.noFolderCiphers = this.state.noFolderCiphers; - } - if (this.state.ciphers != null) { - this.ciphers = this.state.ciphers; - } - if (this.state.collectionCounts != null) { - this.collectionCounts = this.state.collectionCounts; - } - if (this.state.folderCounts != null) { - this.folderCounts = this.state.folderCounts; - } - if (this.state.typeCounts != null) { - this.typeCounts = this.state.typeCounts; - } - if (this.state.folders != null) { - this.folders = this.state.folders; - } - if (this.state.collections != null) { - this.collections = this.state.collections; - } - if (this.state.deletedCount != null) { - this.deletedCount = this.state.deletedCount; - } - - return true; - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.html b/apps/browser/src/vault/popup/components/vault/vault-items.component.html deleted file mode 100644 index f10688554d9..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.html +++ /dev/null @@ -1,123 +0,0 @@ -
-
- -
-

{{ "myVault" | i18n }}

- -
- -
-
-
- - -
-

- {{ "folders" | i18n }} -

-
- -
-
-
-

- {{ "collections" | i18n }} -

-
- -
-
-
- -
- -
- - - -

{{ "noItemsInList" | i18n }}

- -
-
-
- -
-

- {{ groupingTitle }} - {{ isSearching() ? ciphers.length : ciphers.length }} -

-
- -
-
-
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts deleted file mode 100644 index 27d36cbc2f1..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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 { CipherType } from "@bitwarden/common/vault/enums"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserComponentState } from "../../../../models/browserComponentState"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const ComponentId = "VaultItemsComponent"; - -@Component({ - selector: "app-vault-items", - templateUrl: "vault-items.component.html", -}) -export class VaultItemsComponent extends BaseVaultItemsComponent implements OnInit, OnDestroy { - groupingTitle: string; - state: BrowserComponentState; - folderId: string = null; - collectionId: string = null; - type: CipherType = null; - nestedFolders: TreeNode[]; - nestedCollections: TreeNode[]; - searchTypeSearch = false; - showOrganizations = false; - vaultFilter: VaultFilter; - deleted = true; - noneFolder = false; - showVaultFilter = false; - - private selectedTimeout: number; - private preventSelected = false; - private applySavedState = true; - private scrollingContainer = "cdk-virtual-scroll-viewport"; - - constructor( - searchService: SearchService, - private organizationService: OrganizationService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private stateService: VaultBrowserStateService, - private i18nService: I18nService, - private collectionService: CollectionService, - private platformUtilsService: PlatformUtilsService, - cipherService: CipherService, - private vaultFilterService: VaultFilterService, - ) { - super(searchService, cipherService); - this.applySavedState = - (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith("/ciphers"); - } - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showOrganizations = await this.organizationService.hasOrganizations(); - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (this.applySavedState) { - this.state = await this.stateService.getBrowserVaultItemsComponentState(); - if (this.state?.searchText) { - this.searchText = this.state.searchText; - } - } - - if (params.deleted) { - this.showVaultFilter = true; - this.groupingTitle = this.i18nService.t("trash"); - this.searchPlaceholder = this.i18nService.t("searchTrash"); - await this.load(this.buildFilter(), true); - } else if (params.type) { - this.showVaultFilter = true; - this.searchPlaceholder = this.i18nService.t("searchType"); - this.type = parseInt(params.type, null); - switch (this.type) { - case CipherType.Login: - this.groupingTitle = this.i18nService.t("logins"); - break; - case CipherType.Card: - this.groupingTitle = this.i18nService.t("cards"); - break; - case CipherType.Identity: - this.groupingTitle = this.i18nService.t("identities"); - break; - case CipherType.SecureNote: - this.groupingTitle = this.i18nService.t("secureNotes"); - break; - case CipherType.SshKey: - this.groupingTitle = this.i18nService.t("sshKeys"); - break; - default: - break; - } - await this.load(this.buildFilter()); - } else if (params.folderId) { - this.showVaultFilter = true; - this.folderId = params.folderId === "none" ? null : params.folderId; - this.searchPlaceholder = this.i18nService.t("searchFolder"); - if (this.folderId != null) { - this.showOrganizations = false; - const folderNode = await this.vaultFilterService.getFolderNested(this.folderId); - if (folderNode != null && folderNode.node != null) { - this.groupingTitle = folderNode.node.name; - this.nestedFolders = - folderNode.children != null && folderNode.children.length > 0 - ? folderNode.children - : null; - } - } else { - this.noneFolder = true; - this.groupingTitle = this.i18nService.t("noneFolder"); - } - await this.load(this.buildFilter()); - } else if (params.collectionId) { - this.showVaultFilter = false; - this.collectionId = params.collectionId; - this.searchPlaceholder = this.i18nService.t("searchCollection"); - const collectionNode = await this.collectionService.getNested(this.collectionId); - if (collectionNode != null && collectionNode.node != null) { - this.groupingTitle = collectionNode.node.name; - this.nestedCollections = - collectionNode.children != null && collectionNode.children.length > 0 - ? collectionNode.children - : null; - } - await this.load( - (c) => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1, - ); - } else { - this.showVaultFilter = true; - this.groupingTitle = this.i18nService.t("allItems"); - await this.load(this.buildFilter()); - } - - if (this.applySavedState && this.state != null) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state.scrollY, { - delay: 0, - containerSelector: this.scrollingContainer, - }); - } - await this.stateService.setBrowserVaultItemsComponentState(null); - }); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - window.setTimeout(() => { - // 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.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // 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.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - } - - selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - super.selectCipher(cipher); - // 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(["/view-cipher"], { - queryParams: { cipherId: cipher.id, collectionId: this.collectionId }, - }); - } - this.preventSelected = false; - }, 200); - } - - selectFolder(folder: FolderView) { - if (folder.id != null) { - // 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(["/ciphers"], { queryParams: { folderId: folder.id } }); - } - } - - selectCollection(collection: CollectionView) { - // 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(["/ciphers"], { queryParams: { collectionId: collection.id } }); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); - // 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 - BrowserApi.createNewTab(cipher.login.launchUri); - if (BrowserPopupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - addCipher() { - if (this.deleted) { - return false; - } - super.addCipher(); - // 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(["/add-cipher"], { - queryParams: { - folderId: this.folderId, - type: this.type, - collectionId: this.collectionId, - selectedVault: this.vaultFilter.selectedOrganizationId, - }, - }); - } - - back() { - (window as any).routeDirection = "b"; - this.location.back(); - } - - showGroupings() { - return ( - !this.isSearching() && - ((this.nestedFolders && this.nestedFolders.length) || - (this.nestedCollections && this.nestedCollections.length)) - ); - } - - async changeVaultSelection() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - await this.load(this.buildFilter(), this.deleted); - } - - private buildFilter(): (cipher: CipherView) => boolean { - return (cipher) => { - let cipherPassesFilter = true; - if (this.deleted && cipherPassesFilter) { - cipherPassesFilter = cipher.isDeleted; - } - if (this.type != null && cipherPassesFilter) { - cipherPassesFilter = cipher.type === this.type; - } - if (this.folderId != null && this.folderId != "none" && cipherPassesFilter) { - cipherPassesFilter = cipher.folderId === this.folderId; - } - if (this.noneFolder) { - cipherPassesFilter = cipher.folderId == null; - } - if (this.collectionId != null && cipherPassesFilter) { - cipherPassesFilter = - cipher.collectionIds != null && cipher.collectionIds.indexOf(this.collectionId) > -1; - } - if (this.vaultFilter.selectedOrganizationId != null && cipherPassesFilter) { - cipherPassesFilter = cipher.organizationId === this.vaultFilter.selectedOrganizationId; - } - if (this.vaultFilter.myVaultOnly && cipherPassesFilter) { - cipherPassesFilter = cipher.organizationId === null; - } - return cipherPassesFilter; - }; - } - - private async saveState() { - this.state = { - scrollY: BrowserPopupUtils.getContentScrollY(window, this.scrollingContainer), - searchText: this.searchText, - }; - await this.stateService.setBrowserVaultItemsComponentState(this.state); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-select.component.html b/apps/browser/src/vault/popup/components/vault/vault-select.component.html deleted file mode 100644 index 4f6ce3a11e6..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-select.component.html +++ /dev/null @@ -1,82 +0,0 @@ - -
- - - - - - -
-
diff --git a/apps/browser/src/vault/popup/components/vault/vault-select.component.ts b/apps/browser/src/vault/popup/components/vault/vault-select.component.ts deleted file mode 100644 index 6780cd57929..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-select.component.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition, Overlay, OverlayRef } from "@angular/cdk/overlay"; -import { TemplatePortal } from "@angular/cdk/portal"; -import { - Component, - ElementRef, - EventEmitter, - HostListener, - OnDestroy, - OnInit, - Output, - TemplateRef, - ViewChild, - ViewContainerRef, -} from "@angular/core"; -import { - BehaviorSubject, - combineLatest, - concatMap, - map, - merge, - Observable, - Subject, - takeUntil, -} from "rxjs"; - -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -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 { VaultFilterService } from "../../../services/vault-filter.service"; - -@Component({ - selector: "app-vault-select", - templateUrl: "vault-select.component.html", - animations: [ - trigger("transformPanel", [ - state( - "void", - style({ - opacity: 0, - }), - ), - transition( - "void => open", - animate( - "100ms linear", - style({ - opacity: 1, - }), - ), - ), - transition("* => void", animate("100ms linear", style({ opacity: 0 }))), - ]), - ], -}) -export class VaultSelectComponent implements OnInit, OnDestroy { - @Output() onVaultSelectionChanged = new EventEmitter(); - - @ViewChild("toggleVaults", { read: ElementRef }) - buttonRef: ElementRef; - @ViewChild("vaultSelectorTemplate", { read: TemplateRef }) templateRef: TemplateRef; - - private _selectedVault = new BehaviorSubject(null); - - isOpen = false; - loaded = false; - organizations$: Observable; - selectedVault$: Observable = this._selectedVault.asObservable(); - - enforcePersonalOwnership = false; - overlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - }, - ]; - - private overlayRef: OverlayRef; - private _destroy = new Subject(); - - shouldShow(organizations: Organization[]): boolean { - return ( - (organizations.length > 0 && !this.enforcePersonalOwnership) || - (organizations.length > 1 && this.enforcePersonalOwnership) - ); - } - - constructor( - private vaultFilterService: VaultFilterService, - private i18nService: I18nService, - private overlay: Overlay, - private viewContainerRef: ViewContainerRef, - private platformUtilsService: PlatformUtilsService, - private organizationService: OrganizationService, - private policyService: PolicyService, - ) {} - - @HostListener("document:keydown.escape", ["$event"]) - handleKeyboardEvent(event: KeyboardEvent) { - if (this.isOpen) { - event.preventDefault(); - this.close(); - } - } - - async ngOnInit() { - this.organizations$ = this.organizationService.memberOrganizations$ - .pipe(takeUntil(this._destroy)) - .pipe(map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name")))); - - combineLatest([ - this.organizations$, - this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), - ]) - .pipe( - concatMap(async ([organizations, enforcePersonalOwnership]) => { - this.enforcePersonalOwnership = enforcePersonalOwnership; - - if (this.shouldShow(organizations)) { - if (this.enforcePersonalOwnership && !this.vaultFilterService.vaultFilter.myVaultOnly) { - const firstOrganization = organizations[0]; - this._selectedVault.next(firstOrganization.name); - this.vaultFilterService.setVaultFilter(firstOrganization.id); - } else if (this.vaultFilterService.vaultFilter.myVaultOnly) { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.myVault)); - } else if (this.vaultFilterService.vaultFilter.selectedOrganizationId != null) { - const selectedOrganization = organizations.find( - (o) => o.id === this.vaultFilterService.vaultFilter.selectedOrganizationId, - ); - this._selectedVault.next(selectedOrganization.name); - } else { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.allVaults)); - } - } - }), - ) - .pipe(takeUntil(this._destroy)) - .subscribe(); - - this.loaded = true; - } - - ngOnDestroy(): void { - this._destroy.next(); - this._destroy.complete(); - this._selectedVault.complete(); - } - - openOverlay() { - const viewPortHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - const positionStrategyBuilder = this.overlay.position(); - - const positionStrategy = positionStrategyBuilder - .flexibleConnectedTo(this.buttonRef.nativeElement) - .withFlexibleDimensions(true) - .withPush(true) - .withViewportMargin(10) - .withGrowAfterOpen(true) - .withPositions(this.overlayPosition); - - this.overlayRef = this.overlay.create({ - hasBackdrop: true, - positionStrategy, - maxHeight: viewPortHeight - 160, - backdropClass: "cdk-overlay-transparent-backdrop", - scrollStrategy: this.overlay.scrollStrategies.close(), - }); - - const templatePortal = new TemplatePortal(this.templateRef, this.viewContainerRef); - this.overlayRef.attach(templatePortal); - this.isOpen = true; - - // Handle closing - merge( - this.overlayRef.outsidePointerEvents(), - this.overlayRef.backdropClick(), - this.overlayRef.detachments(), - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - ).subscribe(() => { - this.close(); - }); - } - - close() { - if (this.overlayRef) { - this.overlayRef.dispose(); - this.overlayRef = undefined; - } - this.isOpen = false; - } - - selectOrganization(organization: Organization) { - if (!organization.enabled) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("disabledOrganizationFilterError"), - ); - } else { - this._selectedVault.next(organization.name); - this.vaultFilterService.setVaultFilter(organization.id); - this.onVaultSelectionChanged.emit(); - this.close(); - } - } - selectAllVaults() { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.allVaults)); - this.vaultFilterService.setVaultFilter(this.vaultFilterService.allVaults); - this.onVaultSelectionChanged.emit(); - this.close(); - } - selectMyVault() { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.myVault)); - this.vaultFilterService.setVaultFilter(this.vaultFilterService.myVault); - this.onVaultSelectionChanged.emit(); - this.close(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html b/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html deleted file mode 100644 index 4fbca28734b..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html +++ /dev/null @@ -1,98 +0,0 @@ - -

- {{ "customFields" | i18n }} -

-
-
-
- {{ field.name }} - {{ field.name }} -
- {{ field.value || " " }} -
-
- {{ field.maskedValue }} - - -
-
- - - {{ field.value }} -
-
-
- - {{ "linkedValue" | i18n }} -
- {{ cipher.linkedFieldI18nKey(field.linkedId) | i18n }} -
-
-
- - - -
-
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts b/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts deleted file mode 100644 index 249f83c4444..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from "@angular/core"; - -import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "@bitwarden/angular/vault/components/view-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; - -@Component({ - selector: "app-vault-view-custom-fields", - templateUrl: "view-custom-fields.component.html", -}) -export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { - constructor(eventCollectionService: EventCollectionService) { - super(eventCollectionService); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/view.component.html b/apps/browser/src/vault/popup/components/vault/view.component.html deleted file mode 100644 index 57a5d007d8a..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view.component.html +++ /dev/null @@ -1,719 +0,0 @@ -
-
- -
-

- {{ "viewItem" | i18n }} -

-
- -
-
-
-
-

- {{ "itemInformation" | i18n }} -

-
-
- - -
- -
-
-
- - -
-
- -
-
-
-
- {{ "password" | i18n }} -
- {{ cipher.login.maskedPassword }} -
-
-
-
-
- - - - -
-
- - -
-
-
- {{ "typePasskey" | i18n }} - {{ fido2CredentialCreationDateValue }} -
-
-
- -
-
- {{ "verificationCodeTotp" | i18n }} - {{ totpCodeFormatted }} -
- -
- -
-
-
-
- {{ "verificationCodeTotp" | i18n }} - - - {{ "premiumSubcriptionRequired" | i18n }} - - -
-
-
- -
-
- {{ "cardholderName" | i18n }} - {{ cipher.card.cardholderName }} -
-
-
- {{ "number" | i18n }} - {{ - cipher.card.maskedNumber | creditCardNumber: cipher.card.brand - }} - {{ - cipher.card.number | creditCardNumber: cipher.card.brand - }} -
-
- - -
-
-
- {{ "brand" | i18n }} - {{ cipher.card.brand }} -
-
- {{ "expiration" | i18n }} - {{ cipher.card.expiration }} -
-
-
- {{ "securityCode" | i18n }} - {{ cipher.card.maskedCode }} - {{ cipher.card.code }} -
-
- - -
-
-
- -
-
- {{ "identityName" | i18n }} - {{ cipher.identity.fullName }} -
-
- {{ "username" | i18n }} - {{ cipher.identity.username }} -
-
- {{ "company" | i18n }} - {{ cipher.identity.company }} -
-
- {{ "ssn" | i18n }} - {{ cipher.identity.ssn }} -
-
- {{ "passportNumber" | i18n }} - {{ cipher.identity.passportNumber }} -
-
- {{ "licenseNumber" | i18n }} - {{ cipher.identity.licenseNumber }} -
-
- {{ "email" | i18n }} - {{ cipher.identity.email }} -
-
- {{ "phone" | i18n }} - {{ cipher.identity.phone }} -
-
- {{ "address" | i18n }} -
{{ cipher.identity.address1 }}
-
{{ cipher.identity.address2 }}
-
{{ cipher.identity.address3 }}
-
{{ cipher.identity.fullAddressPart2 }}
-
{{ cipher.identity.country }}
-
-
- -
-
- - {{ "sshPrivateKey" | i18n }} - -
-
-
- - {{ "sshPublicKey" | i18n }} - {{ cipher.sshKey.publicKey }} -
-
- - {{ "sshFingerprint" | i18n }} - {{ cipher.sshKey.keyFingerprint }} -
-
-
-
-
-
-
-
- - - - - -
-
- - -
-
-
-
-
-
-
- - -
-
-
-
-

- -

-
-
- -
-
-
-
- -
-
-

- {{ "attachments" | i18n }} -

-
- -
-
-
-
- - - - - - -
-
-
- -
-
diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts deleted file mode 100644 index e45d0556c2f..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ /dev/null @@ -1,441 +0,0 @@ -import { DatePipe, Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, firstValueFrom, takeUntil, Subscription } from "rxjs"; -import { first, map } from "rxjs/operators"; - -import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -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 { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.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"; -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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { BrowserFido2UserInterfaceSession } from "../../../../autofill/fido2/services/browser-fido2-user-interface.service"; -import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; -import { closeViewVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; - -const BroadcasterSubscriptionId = "ChildViewComponent"; - -export const AUTOFILL_ID = "autofill"; -export const SHOW_AUTOFILL_BUTTON = "show-autofill-button"; -export const COPY_USERNAME_ID = "copy-username"; -export const COPY_PASSWORD_ID = "copy-password"; -export const COPY_VERIFICATION_CODE_ID = "copy-totp"; - -type CopyAction = - | typeof COPY_USERNAME_ID - | typeof COPY_PASSWORD_ID - | typeof COPY_VERIFICATION_CODE_ID; -type LoadAction = typeof AUTOFILL_ID | typeof SHOW_AUTOFILL_BUTTON | CopyAction; - -@Component({ - selector: "app-vault-view", - templateUrl: "view.component.html", -}) -export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy { - showAttachments = true; - pageDetails: any[] = []; - tab: any; - senderTabId?: number; - loadAction?: LoadAction; - private static readonly copyActions = new Set([ - COPY_USERNAME_ID, - COPY_PASSWORD_ID, - COPY_VERIFICATION_CODE_ID, - ]); - uilocation?: "popout" | "popup" | "sidebar" | "tab"; - loadPageDetailsTimeout: number; - inPopout = false; - cipherType = CipherType; - private fido2PopoutSessionData$ = fido2PopoutSessionData$(); - private collectPageDetailsSubscription: Subscription; - - private destroy$ = new Subject(); - - constructor( - cipherService: CipherService, - folderService: FolderService, - totpService: TotpServiceAbstraction, - tokenService: TokenService, - i18nService: I18nService, - keyService: KeyService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - broadcasterService: BroadcasterService, - ngZone: NgZone, - changeDetectorRef: ChangeDetectorRef, - stateService: StateService, - eventCollectionService: EventCollectionService, - private autofillService: AutofillService, - private messagingService: MessagingService, - apiService: ApiService, - passwordRepromptService: PasswordRepromptService, - logService: LogService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - datePipe: DatePipe, - accountService: AccountService, - billingAccountProfileStateService: BillingAccountProfileStateService, - cipherAuthorizationService: CipherAuthorizationService, - ) { - super( - cipherService, - folderService, - totpService, - tokenService, - i18nService, - keyService, - encryptService, - platformUtilsService, - auditService, - window, - broadcasterService, - ngZone, - changeDetectorRef, - eventCollectionService, - apiService, - passwordRepromptService, - logService, - stateService, - fileDownloadService, - dialogService, - datePipe, - accountService, - billingAccountProfileStateService, - cipherAuthorizationService, - ); - } - - ngOnInit() { - this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((value) => { - this.loadAction = value?.action; - this.senderTabId = parseInt(value?.senderTabId, 10) || undefined; - this.uilocation = value?.uilocation; - }); - - this.inPopout = this.uilocation === "popout" || BrowserPopupUtils.inPopout(window); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } - - if (params.collectionId) { - this.collectionId = params.collectionId; - } - - if (!params.cipherId) { - // 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.close(); - } - - await this.load(); - }); - - super.ngOnInit(); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "tabChanged": - case "windowChanged": - if (this.loadPageDetailsTimeout != null) { - window.clearTimeout(this.loadPageDetailsTimeout); - } - this.loadPageDetailsTimeout = window.setTimeout(() => this.loadPageDetails(), 500); - break; - default: - break; - } - }); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - super.ngOnDestroy(); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async load() { - await super.load(); - await this.loadPageDetails(); - await this.handleLoadAction(); - } - - async edit() { - if (this.cipher.isDeleted) { - return false; - } - if (!(await super.edit())) { - return false; - } - - // 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(["/edit-cipher"], { - queryParams: { - cipherId: this.cipher.id, - type: this.cipher.type, - isNew: false, - collectionId: this.collectionId, - }, - }); - return true; - } - - async clone() { - if (this.cipher.isDeleted) { - return false; - } - - if (!(await super.clone())) { - return false; - } - - // 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(["/clone-cipher"], { - queryParams: { - cloneMode: true, - cipherId: this.cipher.id, - }, - }); - return true; - } - - async share() { - if (!(await super.share())) { - return false; - } - - if (this.cipher.organizationId == null) { - // 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(["/share-cipher"], { - replaceUrl: true, - queryParams: { cipherId: this.cipher.id }, - }); - } - return true; - } - - async fillCipher() { - const didAutofill = await this.doAutofill(); - if (didAutofill) { - this.platformUtilsService.showToast("success", null, this.i18nService.t("autoFillSuccess")); - } - - return didAutofill; - } - - async fillCipherAndSave() { - const didAutofill = await this.doAutofill(); - - if (didAutofill) { - if (this.tab == null) { - throw new Error("No tab found."); - } - - if (this.cipher.login.uris == null) { - this.cipher.login.uris = []; - } else { - if (this.cipher.login.uris.some((uri) => uri.uri === this.tab.url)) { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("autoFillSuccessAndSavedUri"), - ); - return; - } - } - - const loginUri = new LoginUriView(); - loginUri.uri = this.tab.url; - this.cipher.login.uris.push(loginUri); - - try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const cipher: Cipher = await this.cipherService.encrypt(this.cipher, activeUserId); - await this.cipherService.updateWithServer(cipher); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("autoFillSuccessAndSavedUri"), - ); - this.messagingService.send("editedCipher"); - } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); - } - } - } - - async restore() { - if (!this.cipher.isDeleted) { - return false; - } - if (await super.restore()) { - // 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.close(); - return true; - } - return false; - } - - async delete() { - if (await super.delete()) { - this.messagingService.send("deletedCipher"); - // 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.close(); - return true; - } - return false; - } - - async close() { - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - if (this.inPopout && sessionData.isFido2Session) { - BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId); - return; - } - - if ( - BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.viewVaultItem) && - this.senderTabId - ) { - // 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 - BrowserApi.focusTab(this.senderTabId); - // 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 - closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`); - return; - } - - this.location.back(); - } - - private async loadPageDetails() { - this.collectPageDetailsSubscription?.unsubscribe(); - this.pageDetails = []; - this.tab = this.senderTabId - ? await BrowserApi.getTab(this.senderTabId) - : await BrowserApi.getTabFromCurrentWindow(); - - if (!this.tab) { - return; - } - - this.collectPageDetailsSubscription = this.autofillService - .collectPageDetailsFromTab$(this.tab) - .pipe(takeUntil(this.destroy$)) - .subscribe((pageDetails) => (this.pageDetails = pageDetails)); - } - - private async doAutofill() { - const originalTabURL = this.tab.url?.length && new URL(this.tab.url); - - if (!(await this.promptPassword())) { - return false; - } - - const currentTabURL = this.tab.url?.length && new URL(this.tab.url); - - const originalTabHostPath = - originalTabURL && `${originalTabURL.origin}${originalTabURL.pathname}`; - const currentTabHostPath = currentTabURL && `${currentTabURL.origin}${currentTabURL.pathname}`; - - const tabUrlChanged = originalTabHostPath !== currentTabHostPath; - - if (this.pageDetails == null || this.pageDetails.length === 0 || tabUrlChanged) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - return false; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - tab: this.tab, - cipher: this.cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - allowTotpAutofill: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - this.changeDetectorRef.detectChanges(); - return false; - } - - return true; - } - - private async handleLoadAction() { - if (!this.loadAction || this.loadAction === SHOW_AUTOFILL_BUTTON) { - return; - } - - let loadActionSuccess = false; - if (this.loadAction === AUTOFILL_ID) { - loadActionSuccess = await this.fillCipher(); - } - - if (ViewComponent.copyActions.has(this.loadAction)) { - const { username, password } = this.cipher.login; - const copyParams: Record> = { - [COPY_USERNAME_ID]: { value: username, type: "username", name: "Username" }, - [COPY_PASSWORD_ID]: { value: password, type: "password", name: "Password" }, - [COPY_VERIFICATION_CODE_ID]: { - value: this.totpCode, - type: "verificationCodeTotp", - name: "TOTP", - }, - }; - const { value, type, name } = copyParams[this.loadAction as CopyAction]; - loadActionSuccess = await this.copy(value, type, name); - } - - if (this.inPopout) { - setTimeout(() => this.close(), loadActionSuccess ? 1000 : 0); - } - } -} diff --git a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts index 70993482046..156f8492275 100644 --- a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts +++ b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Overlay } from "@angular/cdk/overlay"; import { inject, Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; diff --git a/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts b/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts index 2c9afacffd7..2b309e8f817 100644 --- a/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts +++ b/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts @@ -2,6 +2,7 @@ import { TestBed } from "@angular/core/testing"; import qrcodeParser from "qrcode-parser"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { BrowserTotpCaptureService } from "./browser-totp-capture.service"; @@ -13,12 +14,14 @@ describe("BrowserTotpCaptureService", () => { let testBed: TestBed; let service: BrowserTotpCaptureService; let mockCaptureVisibleTab: jest.SpyInstance; + let mockBrowserPopupUtilsInPopout: jest.SpyInstance; const validTotpUrl = "otpauth://totp/label?secret=123"; beforeEach(() => { mockCaptureVisibleTab = jest.spyOn(BrowserApi, "captureVisibleTab"); mockCaptureVisibleTab.mockResolvedValue("screenshot"); + mockBrowserPopupUtilsInPopout = jest.spyOn(BrowserPopupUtils, "inPopout"); testBed = TestBed.configureTestingModule({ providers: [BrowserTotpCaptureService], @@ -66,4 +69,16 @@ describe("BrowserTotpCaptureService", () => { expect(result).toBeNull(); }); + + describe("canCaptureTotp", () => { + it("should return true when not in a popout window", () => { + mockBrowserPopupUtilsInPopout.mockReturnValue(false); + expect(service.canCaptureTotp({} as Window)).toBe(true); + }); + + it("should return false when in a popout window", () => { + mockBrowserPopupUtilsInPopout.mockReturnValue(true); + expect(service.canCaptureTotp({} as Window)).toBe(false); + }); + }); }); diff --git a/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts b/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts index 3f8ba61ed36..ac73b271c84 100644 --- a/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts +++ b/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts @@ -4,6 +4,7 @@ import qrcodeParser from "qrcode-parser"; import { TotpCaptureService } from "@bitwarden/vault"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; /** * Implementation of TotpCaptureService for the browser which captures the @@ -20,4 +21,8 @@ export class BrowserTotpCaptureService implements TotpCaptureService { } return null; } + + canCaptureTotp(window: Window) { + return !BrowserPopupUtils.inPopout(window); + } } diff --git a/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts b/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts index ded4686477e..5024b960d9c 100644 --- a/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts +++ b/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts @@ -2,6 +2,8 @@ import { TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + import { BrowserViewPasswordHistoryService } from "./browser-view-password-history.service"; describe("BrowserViewPasswordHistoryService", () => { @@ -19,9 +21,9 @@ describe("BrowserViewPasswordHistoryService", () => { describe("viewPasswordHistory", () => { it("navigates to the password history screen", async () => { - await service.viewPasswordHistory("test"); + await service.viewPasswordHistory({ id: "cipher-id" } as CipherView); expect(router.navigate).toHaveBeenCalledWith(["/cipher-password-history"], { - queryParams: { cipherId: "test" }, + queryParams: { cipherId: "cipher-id" }, }); }); }); diff --git a/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts b/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts index 6b57b0b625e..5e400da9de5 100644 --- a/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts +++ b/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts @@ -1,7 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject } from "@angular/core"; import { Router } from "@angular/router"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; /** * This class handles the premium upgrade process for the browser extension. @@ -12,7 +15,9 @@ export class BrowserViewPasswordHistoryService implements ViewPasswordHistorySer /** * Navigates to the password history screen. */ - async viewPasswordHistory(cipherId: string) { - await this.router.navigate(["/cipher-password-history"], { queryParams: { cipherId } }); + async viewPasswordHistory(cipher: CipherView) { + await this.router.navigate(["/cipher-password-history"], { + queryParams: { cipherId: cipher.id }, + }); } } diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index effadad07fb..2dad1e3034c 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -4,7 +4,9 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -19,6 +21,9 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { AutoFillOptions, AutofillService, @@ -40,12 +45,15 @@ describe("VaultPopupAutofillService", () => { // Create mocks for VaultPopupAutofillService const mockAutofillService = mock(); + const mockDomainSettingsService = mock(); const mockI18nService = mock(); const mockToastService = mock(); const mockPlatformUtilsService = mock(); const mockPasswordRepromptService = mock(); const mockCipherService = mock(); const mockMessagingService = mock(); + const mockInlineMenuFieldQualificationService = mock(); + const mockLogService = mock(); const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -53,12 +61,19 @@ describe("VaultPopupAutofillService", () => { beforeEach(() => { jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false); jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockCurrentTab); + jest + .spyOn(mockInlineMenuFieldQualificationService, "isFieldForCreditCardForm") + .mockReturnValue(true); + jest + .spyOn(mockInlineMenuFieldQualificationService, "isFieldForIdentityForm") + .mockReturnValue(true); mockAutofillService.collectPageDetailsFromTab$.mockReturnValue(new BehaviorSubject([])); testBed = TestBed.configureTestingModule({ providers: [ { provide: AutofillService, useValue: mockAutofillService }, + { provide: DomainSettingsService, useValue: mockDomainSettingsService }, { provide: I18nService, useValue: mockI18nService }, { provide: ToastService, useValue: mockToastService }, { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, @@ -70,6 +85,14 @@ describe("VaultPopupAutofillService", () => { provide: AccountService, useValue: accountService, }, + { + provide: InlineMenuFieldQualificationService, + useValue: mockInlineMenuFieldQualificationService, + }, + { + provide: LogService, + useValue: mockLogService, + }, ], }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index a2e032a54f1..ff282d7a6d0 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { @@ -13,7 +15,9 @@ import { } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -23,6 +27,9 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { AutofillService, PageDetail, @@ -61,6 +68,74 @@ export class VaultPopupAutofillService { shareReplay({ refCount: false, bufferSize: 1 }), ); + currentTabIsOnBlocklist$: Observable = combineLatest([ + this.domainSettingsService.blockedInteractionsUris$, + this.currentAutofillTab$, + ]).pipe( + map(([blockedInteractionsUris, currentTab]) => { + if (blockedInteractionsUris && currentTab?.url?.length) { + const tabURL = new URL(currentTab.url); + const tabIsBlocked = Object.keys(blockedInteractionsUris).includes(tabURL.hostname); + + if (tabIsBlocked) { + return true; + } + } + + return false; + }), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + + showCurrentTabIsBlockedBanner$: Observable = combineLatest([ + this.domainSettingsService.blockedInteractionsUris$, + this.currentAutofillTab$, + ]).pipe( + map(([blockedInteractionsUris, currentTab]) => { + if (blockedInteractionsUris && currentTab?.url?.length) { + const tabURL = new URL(currentTab.url); + const tabIsBlocked = Object.keys(blockedInteractionsUris).includes(tabURL.hostname); + + const showScriptInjectionIsBlockedBanner = + tabIsBlocked && !blockedInteractionsUris[tabURL.hostname]?.bannerIsDismissed; + + return showScriptInjectionIsBlockedBanner; + } + + return false; + }), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + + async dismissCurrentTabIsBlockedBanner() { + try { + const currentTab = await firstValueFrom(this.currentAutofillTab$); + const currentTabURL = currentTab?.url.length && new URL(currentTab.url); + + const currentTabHostname = currentTabURL && currentTabURL.hostname; + + if (!currentTabHostname) { + return; + } + + const blockedURIs = await firstValueFrom(this.domainSettingsService.blockedInteractionsUris$); + const tabIsBlocked = Object.keys(blockedURIs).includes(currentTabHostname); + + if (tabIsBlocked) { + void this.domainSettingsService.setBlockedInteractionsUris({ + ...blockedURIs, + [currentTabHostname as string]: { bannerIsDismissed: true }, + }); + } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + throw new Error( + "There was a problem dismissing the blocked interaction URI notification banner", + ); + } + } + /** * Observable that indicates whether autofill is allowed in the current context. * Autofill is allowed when there is a current tab and the popup is not in a popout window. @@ -77,8 +152,49 @@ export class VaultPopupAutofillService { shareReplay({ refCount: false, bufferSize: 1 }), ); + nonLoginCipherTypesOnPage$: Observable<{ + [CipherType.Card]: boolean; + [CipherType.Identity]: boolean; + }> = this._currentPageDetails$.pipe( + map((pageDetails) => { + let pageHasCardFields = false; + let pageHasIdentityFields = false; + + try { + if (!pageDetails) { + throw Error("No page details were provided"); + } + + for (const details of pageDetails) { + for (const field of details.details.fields) { + if (!pageHasCardFields) { + pageHasCardFields = this.inlineMenuFieldQualificationService.isFieldForCreditCardForm( + field, + details.details, + ); + } + + if (!pageHasIdentityFields) { + pageHasIdentityFields = + this.inlineMenuFieldQualificationService.isFieldForIdentityForm( + field, + details.details, + ); + } + } + } + } catch (error) { + // no-op on failure; do not show extra cipher types + this.logService.warning(error.message); + } + + return { [CipherType.Card]: pageHasCardFields, [CipherType.Identity]: pageHasIdentityFields }; + }), + ); + constructor( private autofillService: AutofillService, + private domainSettingsService: DomainSettingsService, private i18nService: I18nService, private toastService: ToastService, private platformUtilService: PlatformUtilsService, @@ -87,6 +203,8 @@ export class VaultPopupAutofillService { private messagingService: MessagingService, private route: ActivatedRoute, private accountService: AccountService, + private logService: LogService, + private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService, ) { this._currentPageDetails$.subscribe(); } diff --git a/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts b/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts new file mode 100644 index 00000000000..d6bd12c6200 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts @@ -0,0 +1,39 @@ +import { inject, Injectable } from "@angular/core"; +import { map, Observable } from "rxjs"; + +import { + GlobalStateProvider, + KeyDefinition, + VAULT_APPEARANCE, +} from "@bitwarden/common/platform/state"; + +export type CopyButtonDisplayMode = "combined" | "quick"; + +const COPY_BUTTON = new KeyDefinition(VAULT_APPEARANCE, "copyButtons", { + deserializer: (s) => s, +}); + +/** + * Settings service for vault copy button settings + **/ +@Injectable({ providedIn: "root" }) +export class VaultPopupCopyButtonsService { + private readonly DEFAULT_DISPLAY_MODE = "combined"; + private state = inject(GlobalStateProvider).get(COPY_BUTTON); + + displayMode$: Observable = this.state.state$.pipe( + map((state) => state ?? this.DEFAULT_DISPLAY_MODE), + ); + + async setDisplayMode(displayMode: CopyButtonDisplayMode) { + await this.state.update(() => displayMode); + } + + showQuickCopyActions$: Observable = this.displayMode$.pipe( + map((displayMode) => displayMode === "quick"), + ); + + async setShowQuickCopyActions(value: boolean) { + await this.setDisplayMode(value ? "quick" : "combined"); + } +} diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 610d48fdc6f..528aae111cc 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -15,6 +15,9 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; import { VaultPopupAutofillService } from "./vault-popup-autofill.service"; @@ -39,6 +42,7 @@ describe("VaultPopupItemsService", () => { const collectionService = mock(); const vaultAutofillServiceMock = mock(); const syncServiceMock = mock(); + const inlineMenuFieldQualificationServiceMock = mock(); beforeEach(() => { allCiphers = cipherFactory(10); @@ -56,6 +60,7 @@ describe("VaultPopupItemsService", () => { cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); cipherServiceMock.ciphers$ = new BehaviorSubject(null); cipherServiceMock.localData$ = new BehaviorSubject(null); + cipherServiceMock.failedToDecryptCiphers$ = new BehaviorSubject([]); searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => ciphers.filter((c) => ["0", "1"].includes(c.id)), @@ -78,6 +83,11 @@ describe("VaultPopupItemsService", () => { url: "https://example.com", } as chrome.tabs.Tab); + vaultAutofillServiceMock.nonLoginCipherTypesOnPage$ = new BehaviorSubject({ + [CipherType.Card]: true, + [CipherType.Identity]: true, + }); + mockOrg = { id: "org1", name: "Organization 1", @@ -105,6 +115,10 @@ describe("VaultPopupItemsService", () => { { provide: CollectionService, useValue: collectionService }, { provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock }, { provide: SyncService, useValue: syncServiceMock }, + { + provide: InlineMenuFieldQualificationService, + useValue: inlineMenuFieldQualificationServiceMock, + }, ], }); @@ -251,13 +265,6 @@ describe("VaultPopupItemsService", () => { }); }); - it("should sort by last used then by name", (done) => { - service.favoriteCiphers$.subscribe((ciphers) => { - expect(cipherServiceMock.sortCiphersByLastUsedThenName).toHaveBeenCalled(); - done(); - }); - }); - it("should filter favoriteCiphers$ down to search term", (done) => { const cipherList = Object.values(allCiphers); const searchText = "Card 2"; @@ -290,21 +297,6 @@ describe("VaultPopupItemsService", () => { }); }); - it("should sort by last used then by name by default", (done) => { - service.remainingCiphers$.subscribe(() => { - expect(cipherServiceMock.getLocaleSortingFunction).toHaveBeenCalled(); - done(); - }); - }); - - it("should NOT sort by last used then by name when search text is applied", (done) => { - service.applyFilter("Login"); - service.remainingCiphers$.subscribe(() => { - expect(cipherServiceMock.getLocaleSortingFunction).not.toHaveBeenCalled(); - done(); - }); - }); - it("should filter remainingCiphers$ down to search term", (done) => { const cipherList = Object.values(allCiphers); const searchText = "Login"; @@ -371,20 +363,17 @@ describe("VaultPopupItemsService", () => { }); describe("deletedCiphers$", () => { - it("should return deleted ciphers", (done) => { - const ciphers = [ - { id: "1", type: CipherType.Login, name: "Login 1", isDeleted: true }, - { id: "2", type: CipherType.Login, name: "Login 2", isDeleted: true }, - { id: "3", type: CipherType.Login, name: "Login 3", isDeleted: true }, - { id: "4", type: CipherType.Login, name: "Login 4", isDeleted: false }, - ] as CipherView[]; + it("should return deleted ciphers", async () => { + const deletedCipher = new CipherView(); + deletedCipher.deletedDate = new Date(); + const ciphers = [new CipherView(), new CipherView(), new CipherView(), deletedCipher]; cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers); - service.deletedCiphers$.subscribe((deletedCiphers) => { - expect(deletedCiphers.length).toBe(3); - done(); - }); + (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + + const deletedCiphers = await firstValueFrom(service.deletedCiphers$); + expect(deletedCiphers.length).toBe(1); }); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index 20ac3b3de96..fb230df7953 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject, Injectable, NgZone } from "@angular/core"; import { BehaviorSubject, @@ -62,8 +64,13 @@ export class VaultPopupItemsService { private _otherAutoFillTypes$: Observable = combineLatest([ this.vaultSettingsService.showCardsCurrentTab$, this.vaultSettingsService.showIdentitiesCurrentTab$, + this.vaultPopupAutofillService.nonLoginCipherTypesOnPage$, ]).pipe( - map(([showCards, showIdentities]) => { + map(([showCardsSettingEnabled, showIdentitiesSettingEnabled, nonLoginCipherTypesOnPage]) => { + const showCards = showCardsSettingEnabled && nonLoginCipherTypesOnPage[CipherType.Card]; + const showIdentities = + showIdentitiesSettingEnabled && nonLoginCipherTypesOnPage[CipherType.Identity]; + return [ ...(showCards ? [CipherType.Card] : []), ...(showIdentities ? [CipherType.Identity] : []), @@ -83,6 +90,8 @@ export class VaultPopupItemsService { tap(() => this._ciphersLoading$.next()), waitUntilSync(this.syncService), switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), + withLatestFrom(this.cipherService.failedToDecryptCiphers$), + map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -157,16 +166,13 @@ export class VaultPopupItemsService { /** * List of favorite ciphers that are not currently suggested for autofill. - * Ciphers are sorted by last used date, then by name. + * Ciphers are sorted by name. */ favoriteCiphers$: Observable = this.autoFillCiphers$.pipe( withLatestFrom(this._filteredCipherList$), map(([autoFillCiphers, ciphers]) => ciphers.filter((cipher) => cipher.favorite && !autoFillCiphers.includes(cipher)), ), - map((ciphers) => - ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)), - ), shareReplay({ refCount: false, bufferSize: 1 }), ); @@ -186,11 +192,6 @@ export class VaultPopupItemsService { (cipher) => !autoFillCiphers.includes(cipher) && !favoriteCiphers.includes(cipher), ), ), - withLatestFrom(this._hasSearchText$), - map(([ciphers, hasSearchText]) => - // Do not sort alphabetically when there is search text, default to the search service scoring - hasSearchText ? ciphers : ciphers.sort(this.cipherService.getLocaleSortingFunction()), - ), shareReplay({ refCount: false, bufferSize: 1 }), ); @@ -246,8 +247,28 @@ export class VaultPopupItemsService { /** * Observable that contains the list of ciphers that have been deleted. */ - deletedCiphers$: Observable = this._allDecryptedCiphers$.pipe( - map((ciphers) => ciphers.filter((c) => c.isDeleted)), + deletedCiphers$: Observable = this._allDecryptedCiphers$.pipe( + switchMap((ciphers) => + combineLatest([ + this.organizationService.organizations$, + this.collectionService.decryptedCollections$, + ]).pipe( + map(([organizations, collections]) => { + const orgMap = Object.fromEntries(organizations.map((org) => [org.id, org])); + const collectionMap = Object.fromEntries(collections.map((col) => [col.id, col])); + return ciphers + .filter((c) => c.isDeleted) + .map( + (cipher) => + new PopupCipherView( + cipher, + cipher.collectionIds?.map((colId) => collectionMap[colId as CollectionId]), + orgMap[cipher.organizationId as OrganizationId], + ), + ); + }), + ), + ), shareReplay({ refCount: false, bufferSize: 1 }), ); diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 02ad7375f6a..0eb91c6cbe2 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -7,8 +7,12 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -31,7 +35,7 @@ describe("VaultPopupListFiltersService", () => { } as unknown as CollectionService; const folderService = { - folderViews$, + folderViews$: () => folderViews$, } as unknown as FolderService; const cipherService = { @@ -50,12 +54,17 @@ describe("VaultPopupListFiltersService", () => { policyAppliesToActiveUser$: jest.fn(() => policyAppliesToActiveUser$), }; + const state$ = new BehaviorSubject(false); + const update = jest.fn().mockResolvedValue(undefined); + beforeEach(() => { memberOrganizations$.next([]); decryptedCollections$.next([]); policyAppliesToActiveUser$.next(false); policyService.policyAppliesToActiveUser$.mockClear(); + const accountService = mockAccountServiceWith("userId" as UserId); + collectionService.getAllNested = () => Promise.resolve([]); TestBed.configureTestingModule({ providers: [ @@ -83,7 +92,15 @@ describe("VaultPopupListFiltersService", () => { provide: PolicyService, useValue: policyService, }, + { + provide: StateProvider, + useValue: { getGlobal: () => ({ state$, update }) }, + }, { provide: FormBuilder, useClass: FormBuilder }, + { + provide: AccountService, + useValue: accountService, + }, ], }); @@ -102,6 +119,20 @@ describe("VaultPopupListFiltersService", () => { }); }); + describe("numberOfAppliedFilters$", () => { + it("updates as the form value changes", (done) => { + service.numberOfAppliedFilters$.subscribe((number) => { + expect(number).toBe(2); + done(); + }); + + service.filterForm.patchValue({ + organization: { id: "1234" } as Organization, + folder: { id: "folder11" } as FolderView, + }); + }); + }); + describe("organizations$", () => { it('does not add "myVault" to the list of organizations when there are no organizations', (done) => { memberOrganizations$.next([]); @@ -451,4 +482,24 @@ describe("VaultPopupListFiltersService", () => { }); }); }); + + describe("filterVisibilityState", () => { + it("exposes stored state through filterVisibilityState$", (done) => { + state$.next(true); + + service.filterVisibilityState$.subscribe((filterVisibility) => { + expect(filterVisibility).toBeTrue(); + done(); + }); + }); + + it("updates stored filter state", async () => { + await service.updateFilterVisibility(false); + + expect(update).toHaveBeenCalledOnce(); + // Get callback passed to `update` + const updateCallback = update.mock.calls[0][0]; + expect(updateCallback()).toBe(false); + }); + }); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 590807cff60..8455fd587d0 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder } from "@angular/forms"; @@ -6,6 +8,7 @@ import { distinctUntilChanged, map, Observable, + shareReplay, startWith, switchMap, tap, @@ -17,9 +20,15 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + KeyDefinition, + StateProvider, + VAULT_SETTINGS_DISK, +} from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -29,6 +38,10 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { ChipSelectOption } from "@bitwarden/components"; +const FILTER_VISIBILITY_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "filterVisibility", { + deserializer: (obj) => obj, +}); + /** All available cipher filters */ export type PopupListFilter = { organization: Organization | null; @@ -66,6 +79,15 @@ export class VaultPopupListFiltersService { startWith(INITIAL_FILTERS), ) as Observable; + /** Emits the number of applied filters. */ + numberOfAppliedFilters$ = this.filters$.pipe( + map((filters) => Object.values(filters).filter((filter) => Boolean(filter)).length), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + /** Stored state for the visibility of the filters. */ + private filterVisibilityState = this.stateProvider.getGlobal(FILTER_VISIBILITY_KEY); + /** * Static list of ciphers views used in synchronous context */ @@ -81,6 +103,8 @@ export class VaultPopupListFiltersService { map((ciphers) => Object.values(ciphers)), ); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private folderService: FolderService, private cipherService: CipherService, @@ -89,12 +113,17 @@ export class VaultPopupListFiltersService { private collectionService: CollectionService, private formBuilder: FormBuilder, private policyService: PolicyService, + private stateProvider: StateProvider, + private accountService: AccountService, ) { this.filterForm.controls.organization.valueChanges .pipe(takeUntilDestroyed()) .subscribe(this.validateOrganizationChange.bind(this)); } + /** Stored state for the visibility of the filters. */ + filterVisibilityState$ = this.filterVisibilityState.state$; + /** * Observable whose value is a function that filters an array of `CipherView` objects based on the current filters */ @@ -239,60 +268,67 @@ export class VaultPopupListFiltersService { /** * Folder array structured to be directly passed to `ChipSelectComponent` */ - folders$: Observable[]> = combineLatest([ - this.filters$.pipe( - distinctUntilChanged( - (previousFilter, currentFilter) => - // Only update the collections when the organizationId filter changes - previousFilter.organization?.id === currentFilter.organization?.id, - ), - ), - this.folderService.folderViews$, - this.cipherViews$, - ]).pipe( - map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { - if (folders.length === 1 && folders[0].id === null) { - // Do not display folder selections when only the "no folder" option is available. - return [filters, [], cipherViews]; - } + folders$: Observable[]> = this.activeUserId$.pipe( + switchMap((userId) => + combineLatest([ + this.filters$.pipe( + distinctUntilChanged( + (previousFilter, currentFilter) => + // Only update the collections when the organizationId filter changes + previousFilter.organization?.id === currentFilter.organization?.id, + ), + ), + this.folderService.folderViews$(userId), + this.cipherViews$, + ]).pipe( + map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { + if (folders.length === 1 && folders[0].id === null) { + // Do not display folder selections when only the "no folder" option is available. + return [filters, [], cipherViews]; + } - // Sort folders by alphabetic name - folders.sort(Utils.getSortFunction(this.i18nService, "name")); - let arrangedFolders = folders; + // Sort folders by alphabetic name + folders.sort(Utils.getSortFunction(this.i18nService, "name")); + let arrangedFolders = folders; - const noFolder = folders.find((f) => f.id === null); + const noFolder = folders.find((f) => f.id === null); - if (noFolder) { - // Update `name` of the "no folder" option to "Items with no folder" - noFolder.name = this.i18nService.t("itemsWithNoFolder"); + if (noFolder) { + // Update `name` of the "no folder" option to "Items with no folder" + const updatedNoFolder = { + ...noFolder, + name: this.i18nService.t("itemsWithNoFolder"), + }; - // Move the "no folder" option to the end of the list - arrangedFolders = [...folders.filter((f) => f.id !== null), noFolder]; - } - return [filters, arrangedFolders, cipherViews]; - }), - map(([filters, folders, cipherViews]) => { - const organizationId = filters.organization?.id ?? null; + // Move the "no folder" option to the end of the list + arrangedFolders = [...folders.filter((f) => f.id !== null), updatedNoFolder]; + } + return [filters, arrangedFolders, cipherViews]; + }), + map(([filters, folders, cipherViews]) => { + const organizationId = filters.organization?.id ?? null; - // When no org or "My vault" is selected, return all folders - if (organizationId === null || organizationId === MY_VAULT_ID) { - return folders; - } + // When no org or "My vault" is selected, return all folders + if (organizationId === null || organizationId === MY_VAULT_ID) { + return folders; + } - const orgCiphers = cipherViews.filter((c) => c.organizationId === organizationId); + const orgCiphers = cipherViews.filter((c) => c.organizationId === organizationId); - // Return only the folders that have ciphers within the filtered organization - return folders.filter((f) => orgCiphers.some((oc) => oc.folderId === f.id)); - }), - map((folders) => { - const nestedFolders = this.getAllFoldersNested(folders); - return new DynamicTreeNode({ - fullList: folders, - nestedList: nestedFolders, - }); - }), - map((folders) => - folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), + // Return only the folders that have ciphers within the filtered organization + return folders.filter((f) => orgCiphers.some((oc) => oc.folderId === f.id)); + }), + map((folders) => { + const nestedFolders = this.getAllFoldersNested(folders); + return new DynamicTreeNode({ + fullList: folders, + nestedList: nestedFolders, + }); + }), + map((folders) => + folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), + ), + ), ), ); @@ -332,6 +368,11 @@ export class VaultPopupListFiltersService { ), ); + /** Updates the stored state for filter visibility. */ + async updateFilterVisibility(isVisible: boolean): Promise { + await this.filterVisibilityState.update(() => isVisible); + } + /** * Converts the given item into the `ChipSelectOption` structure */ diff --git a/apps/browser/src/vault/popup/services/vault-popup-section.service.ts b/apps/browser/src/vault/popup/services/vault-popup-section.service.ts new file mode 100644 index 00000000000..ed641e0cdf7 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-section.service.ts @@ -0,0 +1,129 @@ +import { computed, effect, inject, Injectable, signal, Signal } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; + +import { + KeyDefinition, + StateProvider, + VAULT_SETTINGS_DISK, +} from "@bitwarden/common/platform/state"; + +import { VaultPopupItemsService } from "./vault-popup-items.service"; + +export type PopupSectionOpen = { + favorites: boolean; + allItems: boolean; +}; + +const SECTION_OPEN_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "sectionOpen", { + deserializer: (obj) => obj, +}); + +const INITIAL_OPEN: PopupSectionOpen = { + favorites: true, + allItems: true, +}; + +@Injectable({ + providedIn: "root", +}) +export class VaultPopupSectionService { + private vaultPopupItemsService = inject(VaultPopupItemsService); + private stateProvider = inject(StateProvider); + + private hasFilterOrSearchApplied = toSignal( + this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => hasFilter)), + ); + + /** + * Used to change the open/close state without persisting it to the local disk. Reflects + * application-applied overrides. + * `null` means there is no current override + */ + private temporaryStateOverride = signal | null>(null); + + constructor() { + effect( + () => { + /** + * auto-open all sections when search or filter is applied, and remove + * override when search or filter is removed + */ + if (this.hasFilterOrSearchApplied()) { + this.temporaryStateOverride.set(INITIAL_OPEN); + } else { + this.temporaryStateOverride.set(null); + } + }, + { + allowSignalWrites: true, + }, + ); + } + + /** + * Stored disk state for the open/close state of the sections. Will be `null` if user has never + * opened/closed a section + */ + private sectionOpenStateProvider = this.stateProvider.getGlobal(SECTION_OPEN_KEY); + + /** + * Stored disk state for the open/close state of the sections, with an initial value provided + * if the stored disk state does not yet exist. + */ + private sectionOpenStoredState = toSignal( + this.sectionOpenStateProvider.state$.pipe(map((sectionOpen) => sectionOpen ?? INITIAL_OPEN)), + // Indicates that the state value is loading + { initialValue: null }, + ); + + /** + * Indicates the current open/close display state of each section, accounting for temporary + * non-persisted overrides. + */ + sectionOpenDisplayState: Signal> = computed(() => ({ + ...this.sectionOpenStoredState(), + ...this.temporaryStateOverride(), + })); + + /** + * Retrieve the open/close display state for a given section. + * + * @param sectionKey section key + */ + getOpenDisplayStateForSection(sectionKey: keyof PopupSectionOpen): Signal { + return computed(() => this.sectionOpenDisplayState()?.[sectionKey]); + } + + /** + * Updates the stored open/close state of a given section. Should be called only when a user action + * is taken directly to change the open/close state. + * + * Removes any current temporary override for the given section, as direct user action should + * supersede any application-applied overrides. + * + * @param sectionKey section key + */ + async updateSectionOpenStoredState( + sectionKey: keyof PopupSectionOpen, + open: boolean, + ): Promise { + await this.sectionOpenStateProvider.update((currentState) => { + return { + ...(currentState ?? INITIAL_OPEN), + [sectionKey]: open, + }; + }); + + this.temporaryStateOverride.update((prev) => { + if (prev !== null) { + return { + ...prev, + [sectionKey]: open, + }; + } + + return prev; + }); + } +} diff --git a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts b/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts index 151f8517d57..f50d6ebc236 100644 --- a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts +++ b/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; @@ -24,8 +26,7 @@ export const GLOBAL_VAULT_UI_ONBOARDING = new KeyDefinition( @Injectable() export class VaultUiOnboardingService { - // TODO: Update this date to the release date of the new Browser UI - private onboardingUiReleaseDate = new Date("2024-07-25"); + private onboardingUiReleaseDate = new Date("2024-12-10"); private vaultUiOnboardingState: GlobalState = this.stateProvider.getGlobal( GLOBAL_VAULT_UI_ONBOARDING, diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.html b/apps/browser/src/vault/popup/settings/appearance-v2.component.html index b267e1c5cb2..3a05d239592 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.html +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -18,6 +18,24 @@ + + {{ "extensionWidth" | i18n }} + + + + + + {{ "compactMode" | i18n }} + {{ "beta" | i18n }} + + + + + {{ "showQuickCopyActions" | i18n }} + + {{ "showNumberOfAutofillSuggestions" | i18n }} diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts index bbd210b65a3..bca83a2fba0 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts @@ -13,8 +13,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { PopupSizeService } from "../../../platform/popup/layout/popup-size.service"; +import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-buttons.service"; import { AppearanceV2Component } from "./appearance-v2.component"; @@ -43,10 +46,19 @@ describe("AppearanceV2Component", () => { const enableBadgeCounter$ = new BehaviorSubject(true); const selectedTheme$ = new BehaviorSubject(ThemeType.Nord); const enableRoutingAnimation$ = new BehaviorSubject(true); + const enableCompactMode$ = new BehaviorSubject(false); + const showQuickCopyActions$ = new BehaviorSubject(false); const setSelectedTheme = jest.fn().mockResolvedValue(undefined); const setShowFavicons = jest.fn().mockResolvedValue(undefined); const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined); const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined); + const setEnableCompactMode = jest.fn().mockResolvedValue(undefined); + const setShowQuickCopyActions = jest.fn().mockResolvedValue(undefined); + + const mockWidthService: Partial = { + width$: new BehaviorSubject("default"), + setWidth: jest.fn().mockResolvedValue(undefined), + }; beforeEach(async () => { setSelectedTheme.mockClear(); @@ -71,6 +83,21 @@ describe("AppearanceV2Component", () => { provide: BadgeSettingsServiceAbstraction, useValue: { enableBadgeCounter$, setEnableBadgeCounter }, }, + { + provide: PopupCompactModeService, + useValue: { enabled$: enableCompactMode$, setEnabled: setEnableCompactMode }, + }, + { + provide: VaultPopupCopyButtonsService, + useValue: { + showQuickCopyActions$, + setShowQuickCopyActions, + } as Partial, + }, + { + provide: PopupSizeService, + useValue: mockWidthService, + }, ], }) .overrideComponent(AppearanceV2Component, { @@ -94,6 +121,9 @@ describe("AppearanceV2Component", () => { enableFavicon: true, enableBadgeCounter: true, theme: ThemeType.Nord, + enableCompactMode: false, + showQuickCopyActions: false, + width: "default", }); }); diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 9d600ec83e8..3aab9f935e4 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -1,5 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, OnInit } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { firstValueFrom } from "rxjs"; @@ -12,14 +14,26 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { CheckboxModule } from "@bitwarden/components"; +import { BadgeModule, CheckboxModule, Option } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CardComponent } from "../../../../../../libs/components/src/card/card.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SelectModule } from "../../../../../../libs/components/src/select/select.module"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { + PopupWidthOption, + PopupSizeService, +} from "../../../platform/popup/layout/popup-size.service"; +import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-buttons.service"; @Component({ standalone: true, @@ -35,14 +49,23 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co SelectModule, ReactiveFormsModule, CheckboxModule, + BadgeModule, ], }) export class AppearanceV2Component implements OnInit { + private compactModeService = inject(PopupCompactModeService); + private copyButtonsService = inject(VaultPopupCopyButtonsService); + private popupSizeService = inject(PopupSizeService); + private i18nService = inject(I18nService); + appearanceForm = this.formBuilder.group({ enableFavicon: false, enableBadgeCounter: true, theme: ThemeType.System, enableAnimations: true, + enableCompactMode: false, + showQuickCopyActions: false, + width: "default" as PopupWidthOption, }); /** To avoid flashes of inaccurate values, only show the form after the entire form is populated. */ @@ -51,6 +74,13 @@ export class AppearanceV2Component implements OnInit { /** Available theme options */ themeOptions: { name: string; value: ThemeType }[]; + /** Available width options */ + protected readonly widthOptions: Option[] = [ + { label: this.i18nService.t("default"), value: "default" }, + { label: this.i18nService.t("wide"), value: "wide" }, + { label: this.i18nService.t("extraWide"), value: "extra-wide" }, + ]; + constructor( private messagingService: MessagingService, private domainSettingsService: DomainSettingsService, @@ -75,6 +105,11 @@ export class AppearanceV2Component implements OnInit { const enableAnimations = await firstValueFrom( this.animationControlService.enableRoutingAnimation$, ); + const enableCompactMode = await firstValueFrom(this.compactModeService.enabled$); + const showQuickCopyActions = await firstValueFrom( + this.copyButtonsService.showQuickCopyActions$, + ); + const width = await firstValueFrom(this.popupSizeService.width$); // Set initial values for the form this.appearanceForm.setValue({ @@ -82,6 +117,9 @@ export class AppearanceV2Component implements OnInit { enableBadgeCounter, theme, enableAnimations, + enableCompactMode, + showQuickCopyActions, + width, }); this.formLoading = false; @@ -109,6 +147,24 @@ export class AppearanceV2Component implements OnInit { .subscribe((enableBadgeCounter) => { void this.updateAnimations(enableBadgeCounter); }); + + this.appearanceForm.controls.enableCompactMode.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enableCompactMode) => { + void this.updateCompactMode(enableCompactMode); + }); + + this.appearanceForm.controls.showQuickCopyActions.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((showQuickCopyActions) => { + void this.updateQuickCopyActions(showQuickCopyActions); + }); + + this.appearanceForm.controls.width.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((width) => { + void this.updateWidth(width); + }); } async updateFavicon(enableFavicon: boolean) { @@ -127,4 +183,16 @@ export class AppearanceV2Component implements OnInit { async updateAnimations(enableAnimations: boolean) { await this.animationControlService.setEnableRoutingAnimation(enableAnimations); } + + async updateCompactMode(enableCompactMode: boolean) { + await this.compactModeService.setEnabled(enableCompactMode); + } + + async updateQuickCopyActions(showQuickCopyActions: boolean) { + await this.copyButtonsService.setShowQuickCopyActions(showQuickCopyActions); + } + + async updateWidth(width: PopupWidthOption) { + await this.popupSizeService.setWidth(width); + } } diff --git a/apps/browser/src/vault/popup/settings/appearance.component.html b/apps/browser/src/vault/popup/settings/appearance.component.html deleted file mode 100644 index a431fc72a1f..00000000000 --- a/apps/browser/src/vault/popup/settings/appearance.component.html +++ /dev/null @@ -1,80 +0,0 @@ -
-
- -
-

- {{ "appearance" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
-
-
diff --git a/apps/browser/src/vault/popup/settings/appearance.component.ts b/apps/browser/src/vault/popup/settings/appearance.component.ts deleted file mode 100644 index 1095b56a75c..00000000000 --- a/apps/browser/src/vault/popup/settings/appearance.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; - -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "vault-appearance", - templateUrl: "appearance.component.html", -}) -export class AppearanceComponent implements OnInit { - enableFavicon = false; - enableBadgeCounter = true; - theme: ThemeType; - themeOptions: any[]; - accountSwitcherEnabled = false; - enableRoutingAnimation: boolean; - - constructor( - private messagingService: MessagingService, - private domainSettingsService: DomainSettingsService, - private badgeSettingsService: BadgeSettingsServiceAbstraction, - i18nService: I18nService, - private themeStateService: ThemeStateService, - private animationControlService: AnimationControlService, - ) { - this.themeOptions = [ - { name: i18nService.t("default"), value: ThemeType.System }, - { name: i18nService.t("light"), value: ThemeType.Light }, - { name: i18nService.t("dark"), value: ThemeType.Dark }, - { name: "Nord", value: ThemeType.Nord }, - { name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark }, - ]; - - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableRoutingAnimation = await firstValueFrom( - this.animationControlService.enableRoutingAnimation$, - ); - - this.enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$); - - this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$); - - this.theme = await firstValueFrom(this.themeStateService.selectedTheme$); - } - - async updateRoutingAnimation() { - await this.animationControlService.setEnableRoutingAnimation(this.enableRoutingAnimation); - } - - async updateFavicon() { - await this.domainSettingsService.setShowFavicons(this.enableFavicon); - } - - async updateBadgeCounter() { - await this.badgeSettingsService.setEnableBadgeCounter(this.enableBadgeCounter); - this.messagingService.send("bgUpdateContextMenu"); - } - - async saveTheme() { - await this.themeStateService.setSelectedTheme(this.theme); - } -} diff --git a/apps/browser/src/vault/popup/settings/folder-add-edit.component.html b/apps/browser/src/vault/popup/settings/folder-add-edit.component.html deleted file mode 100644 index 14393b83ddc..00000000000 --- a/apps/browser/src/vault/popup/settings/folder-add-edit.component.html +++ /dev/null @@ -1,49 +0,0 @@ -
-
-
- -
-

- {{ title }} -

-
- -
-
-
-
-
-
- - -
-
-
-
-
- -
-
-
-
diff --git a/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts b/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts deleted file mode 100644 index 122922a4d2d..00000000000 --- a/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { DialogService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-folder-add-edit", - templateUrl: "folder-add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class FolderAddEditComponent extends BaseFolderAddEditComponent implements OnInit { - constructor( - folderService: FolderService, - folderApiService: FolderApiServiceAbstraction, - accountService: AccountService, - keyService: KeyService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - private router: Router, - private route: ActivatedRoute, - logService: LogService, - dialogService: DialogService, - formBuilder: FormBuilder, - ) { - super( - folderService, - folderApiService, - accountService, - keyService, - i18nService, - platformUtilsService, - logService, - dialogService, - formBuilder, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.folderId) { - this.folderId = params.folderId; - } - await this.init(); - }); - } - - async submit(): Promise { - if (await super.submit()) { - // 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(["/folders"]); - return true; - } - - return false; - } - - async delete(): Promise { - const confirmed = await super.delete(); - if (confirmed) { - // 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(["/folders"]); - } - return confirmed; - } -} 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 eecad04613e..9c202e26fef 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 @@ -4,10 +4,13 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { DialogService } from "@bitwarden/components"; @@ -52,8 +55,9 @@ describe("FoldersV2Component", () => { { provide: PlatformUtilsService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, { provide: LogService, useValue: mock() }, - { provide: FolderService, useValue: { folderViews$ } }, + { provide: FolderService, useValue: { folderViews$: () => folderViews$ } }, { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideComponent(FoldersV2Component, { 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 ce196132f88..8abc3f906c0 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -1,8 +1,10 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { map, Observable } from "rxjs"; +import { filter, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { @@ -13,8 +15,14 @@ import { } from "@bitwarden/components"; import { VaultIcons } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ItemGroupComponent } from "../../../../../../libs/components/src/item/item-group.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ItemModule } from "../../../../../../libs/components/src/item/item.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NoItemsModule } from "../../../../../../libs/components/src/no-items/no-items.module"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -45,18 +53,21 @@ export class FoldersV2Component { folders$: Observable; NoFoldersIcon = VaultIcons.NoFolders; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); constructor( private folderService: FolderService, private dialogService: DialogService, + private accountService: AccountService, ) { - this.folders$ = this.folderService.folderViews$.pipe( + this.folders$ = this.activeUserId$.pipe( + filter((userId): userId is UserId => userId !== null), + switchMap((userId) => this.folderService.folderViews$(userId)), map((folders) => { // Remove the last folder, which is the "no folder" option folder if (folders.length > 0) { return folders.slice(0, folders.length - 1); } - return folders; }), ); diff --git a/apps/browser/src/vault/popup/settings/folders.component.html b/apps/browser/src/vault/popup/settings/folders.component.html deleted file mode 100644 index 47cdb0188d2..00000000000 --- a/apps/browser/src/vault/popup/settings/folders.component.html +++ /dev/null @@ -1,38 +0,0 @@ -
-
- -
-

- {{ "folders" | i18n }} -

-
- -
-
-
- -
-
- -
-
-
- -
-

{{ "noFolders" | i18n }}

-
-
-
diff --git a/apps/browser/src/vault/popup/settings/folders.component.ts b/apps/browser/src/vault/popup/settings/folders.component.ts deleted file mode 100644 index edf7fe939e8..00000000000 --- a/apps/browser/src/vault/popup/settings/folders.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; -import { map, Observable } from "rxjs"; - -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -@Component({ - selector: "app-folders", - templateUrl: "folders.component.html", -}) -export class FoldersComponent { - folders$: Observable; - - constructor( - private folderService: FolderService, - private router: Router, - ) { - this.folders$ = this.folderService.folderViews$.pipe( - map((folders) => { - if (folders.length > 0) { - folders = folders.slice(0, folders.length - 1); - } - - return folders; - }), - ); - } - - folderSelected(folder: FolderView) { - // 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(["/edit-folder"], { queryParams: { folderId: folder.id } }); - } - - addFolder() { - // 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(["/add-folder"]); - } -} diff --git a/apps/browser/src/vault/popup/settings/sync.component.html b/apps/browser/src/vault/popup/settings/sync.component.html deleted file mode 100644 index 6d0a1c31a8b..00000000000 --- a/apps/browser/src/vault/popup/settings/sync.component.html +++ /dev/null @@ -1,35 +0,0 @@ -
-
- -
-

- {{ "sync" | i18n }} -

-
-
-
-
- -

- {{ "lastSync" | i18n }} {{ lastSync }} -

-
-
diff --git a/apps/browser/src/vault/popup/settings/sync.component.ts b/apps/browser/src/vault/popup/settings/sync.component.ts deleted file mode 100644 index 16f388804bb..00000000000 --- a/apps/browser/src/vault/popup/settings/sync.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, OnInit } from "@angular/core"; - -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; - -@Component({ - selector: "app-sync", - templateUrl: "sync.component.html", -}) -export class SyncComponent implements OnInit { - lastSync = "--"; - syncPromise: Promise; - - constructor( - private syncService: SyncService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - ) {} - - async ngOnInit() { - await this.setLastSync(); - } - - async sync() { - this.syncPromise = this.syncService.fullSync(true); - const success = await this.syncPromise; - if (success) { - await this.setLastSync(); - this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingComplete")); - } else { - this.platformUtilsService.showToast("error", null, this.i18nService.t("syncingFailed")); - } - } - - async setLastSync() { - const last = await this.syncService.getLastSync(); - if (last != null) { - this.lastSync = last.toLocaleDateString() + " " + last.toLocaleTimeString(); - } else { - this.lastSync = this.i18nService.t("never"); - } - } -} 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 69322b08c8a..dcbda9fd96a 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 @@ -13,8 +13,23 @@

[appA11yTitle]="'viewItemTitle' | i18n: cipher.name" (click)="onViewCipher(cipher)" > - +
+ +
{{ cipher.name }} + + + {{ cipher.subTitle }} @@ -27,10 +42,15 @@

[bitMenuTriggerFor]="moreOptions" > - - diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index de9d95aab00..c56d1c7d10d 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -1,10 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherId } 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 { @@ -17,7 +20,14 @@ import { ToastService, TypographyModule, } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { + CanDeleteCipherDirective, + DecryptionFailureDialogComponent, + OrgIconDirective, + PasswordRepromptService, +} from "@bitwarden/vault"; + +import { PopupCipherView } from "../../views/popup-cipher.view"; @Component({ selector: "app-trash-list-items-container", @@ -29,17 +39,21 @@ import { PasswordRepromptService } from "@bitwarden/vault"; JslibModule, SectionComponent, SectionHeaderComponent, + CanDeleteCipherDirective, MenuModule, IconButtonModule, + OrgIconDirective, TypographyModule, + DecryptionFailureDialogComponent, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TrashListItemsContainerComponent { /** * The list of trashed items to display. */ @Input() - ciphers: CipherView[] = []; + ciphers: PopupCipherView[] = []; @Input() headerText: string; @@ -54,6 +68,17 @@ export class TrashListItemsContainerComponent { private router: Router, ) {} + /** + * The tooltip text for the organization icon for ciphers that belong to an organization. + */ + orgIconTooltip(cipher: PopupCipherView) { + if (cipher.collectionIds.length > 1) { + return this.i18nService.t("nCollections", cipher.collectionIds.length); + } + + return cipher.collections[0]?.name; + } + async restore(cipher: CipherView) { try { await this.cipherService.restoreWithServer(cipher.id); @@ -101,6 +126,13 @@ export class TrashListItemsContainerComponent { } async onViewCipher(cipher: CipherView) { + if (cipher.decryptionFailure) { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: [cipher.id as CipherId], + }); + return; + } + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); if (!repromptPassed) { return; diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts index b6f77ef6a52..61843de31bc 100644 --- a/apps/browser/src/vault/popup/settings/trash.component.ts +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CalloutModule, NoItemsModule } from "@bitwarden/components"; @@ -8,7 +8,6 @@ import { VaultIcons } from "@bitwarden/vault"; 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 { VaultListItemsContainerComponent } from "../components/vault-v2/vault-list-items-container/vault-list-items-container.component"; import { VaultPopupItemsService } from "../services/vault-popup-items.service"; import { TrashListItemsContainerComponent } from "./trash-list-items-container/trash-list-items-container.component"; @@ -22,11 +21,11 @@ import { TrashListItemsContainerComponent } from "./trash-list-items-container/t PopupPageComponent, PopupHeaderComponent, PopOutComponent, - VaultListItemsContainerComponent, TrashListItemsContainerComponent, CalloutModule, NoItemsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TrashComponent { protected deletedCiphers$ = this.vaultPopupItemsService.deletedCiphers$; diff --git a/apps/browser/src/vault/popup/settings/vault-settings.component.html b/apps/browser/src/vault/popup/settings/vault-settings.component.html deleted file mode 100644 index 4928720e46e..00000000000 --- a/apps/browser/src/vault/popup/settings/vault-settings.component.html +++ /dev/null @@ -1,56 +0,0 @@ - -
- -
-

- {{ "vault" | i18n }} -

-
- -
-
-
-
-
- - - - -
-
-
diff --git a/apps/browser/src/vault/popup/settings/vault-settings.component.ts b/apps/browser/src/vault/popup/settings/vault-settings.component.ts deleted file mode 100644 index a12f6d1d5be..00000000000 --- a/apps/browser/src/vault/popup/settings/vault-settings.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; - -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; - -@Component({ - selector: "vault-settings", - templateUrl: "vault-settings.component.html", -}) -export class VaultSettingsComponent { - constructor( - public messagingService: MessagingService, - private router: Router, - ) {} - - async import() { - await this.router.navigate(["/import"]); - if (await BrowserApi.isPopupOpen()) { - await BrowserPopupUtils.openCurrentPagePopout(window); - } - } -} diff --git a/apps/browser/src/vault/popup/utils/vault-popout-window.ts b/apps/browser/src/vault/popup/utils/vault-popout-window.ts index 36fddadef77..9e347920eb2 100644 --- a/apps/browser/src/vault/popup/utils/vault-popout-window.ts +++ b/apps/browser/src/vault/popup/utils/vault-popout-window.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CipherType } from "@bitwarden/common/vault/enums"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/vault/popup/views/popup-cipher.view.ts b/apps/browser/src/vault/popup/views/popup-cipher.view.ts index 25da61b8cb5..e364aeabc3d 100644 --- a/apps/browser/src/vault/popup/views/popup-cipher.view.ts +++ b/apps/browser/src/vault/popup/views/popup-cipher.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/apps/browser/src/vault/services/fido2-user-verification.service.ts b/apps/browser/src/vault/services/fido2-user-verification.service.ts index 3c91001fd56..8aaababd065 100644 --- a/apps/browser/src/vault/services/fido2-user-verification.service.ts +++ b/apps/browser/src/vault/services/fido2-user-verification.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; diff --git a/apps/browser/src/vault/services/vault-browser-state.service.ts b/apps/browser/src/vault/services/vault-browser-state.service.ts index 17934b3867f..58ce717bf1e 100644 --- a/apps/browser/src/vault/services/vault-browser-state.service.ts +++ b/apps/browser/src/vault/services/vault-browser-state.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, firstValueFrom } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/apps/browser/src/vault/services/vault-filter.service.ts b/apps/browser/src/vault/services/vault-filter.service.ts index 50858076d74..305c7de487b 100644 --- a/apps/browser/src/vault/services/vault-filter.service.ts +++ b/apps/browser/src/vault/services/vault-filter.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionService } from "@bitwarden/admin-console/common"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { VaultFilterService as BaseVaultFilterService } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service"; @@ -22,7 +24,7 @@ export class VaultFilterService extends BaseVaultFilterService { collectionService: CollectionService, policyService: PolicyService, stateProvider: StateProvider, - private accountService: AccountService, + accountService: AccountService, ) { super( organizationService, @@ -31,6 +33,7 @@ export class VaultFilterService extends BaseVaultFilterService { collectionService, policyService, stateProvider, + accountService, ); this.vaultFilter.myVaultOnly = false; this.vaultFilter.selectedOrganizationId = null; diff --git a/apps/browser/store/locales/ar/copy.resx b/apps/browser/store/locales/ar/copy.resx index e1bfa48b44f..9fdfb942100 100644 --- a/apps/browser/store/locales/ar/copy.resx +++ b/apps/browser/store/locales/ar/copy.resx @@ -172,16 +172,16 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - مزامنة خزنتك والوصول إليها من عدة أجهزة + مزامنة خزانتك والوصول إليها من عدة أجهزة - إدارة جميع تسجيلات الدخول وكلمات المرور الخاصة بك من خزنة آمنة + إدارة جميع تسجيلات الدخول وكلمات المرور الخاصة بك من خزانة آمنة قم بملء بيانات تسجيل الدخول تلقائياً وبسرعة في أي موقع تزوره - يمكنك الوصول الى خزنتك بسهولة من قائمة النقر بالزر الأيمن + يمكنك الوصول الى خزانتك بسهولة من قائمة النقر بالزر الأيمن إنشاء كلمات مرور قوية، وعشوائية، وآمنة، تلقائيًا diff --git a/apps/browser/store/locales/gl/copy.resx b/apps/browser/store/locales/gl/copy.resx index 4292ebc4b31..feff0fc99e2 100644 --- a/apps/browser/store/locales/gl/copy.resx +++ b/apps/browser/store/locales/gl/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Xestor de contrasinais + Xestor de Contrasinais Bitwarden - En casa, no traballo ou mentres estás a viaxar, Bitwarden protexe doadamente todos os teus contrasinais, chaves de paso, e información sensíbel. + En casa, no traballo ou en movemento, Bitwarden protexe doadamente todos os teus contrasinais, chaves de paso, e información sensíbel. Recoñecido como o mellor xestor de contrasinais por PCMag, WIRED, The Verge, CNET, G2 e moito máis. @@ -181,7 +181,7 @@ As solucións de xestión de credenciais cifradas de extremo a extremo de Bitwar Autocompleta rapidamente os teus datos de acceso en calquera páxina web que visites - A túa caixa forte tamén é facilmente accesible desde o menú de clic dereito + A túa caixa forte tamén é facilmente accesible dende o menú de clic dereito Xera automaticamente contrasinais fortes, aleatorias e seguras diff --git a/apps/browser/store/locales/id/copy.resx b/apps/browser/store/locales/id/copy.resx index b0791fa3b1f..2d64b4ca542 100644 --- a/apps/browser/store/locales/id/copy.resx +++ b/apps/browser/store/locales/id/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Pengelola Sandi Bitwarden - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Di rumah, di kantor, atau di perjalanan, Bitwarden mengamankan semua kata sandi, kunci sandi, dan informasi sensitif Anda dengan mudah. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Dikenal sebagai pengelola sandi terbaik oleh PCMag, WIRED, The Verge, CNET, G2, dan lainnya! -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. +AMANKAN KEHIDUPAN DIGITAL ANDA +Amankan kehidupan digital Anda dan dapatkan perlindungan dari peretasan data dengan membuat dan menyimpan kata sandi yang unik dan kuat untuk setiap akun. Rawat semuanya dalam brankas kata sandi terenkripsi dari ujung-ke-ujung yang hanya Anda saja yang dapat mengakses. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +AKSES DATA ANDA, DI MANA SAJA, KAPAN SAJA, DI PERANGKAT APAPUN +Kelola, simpan, amankan, dan bagikan tanpa batas dengan mudah kata sandi antar perangkat tak terbatas dan tanpa batasan. -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. +SETIAP ORANG SEBAIKNYA MEMILIKI PERALATAN UNTUK TETAP AMAN KETIKA DARING +Gunakan Bitwarden secara gratis tanpa iklan atau menjual data. Bitwarden percaya setiap orang sebaiknya memiliki kemampuan untuk tetap aman ketika daring. Rencana premium menawarkan akses ke fitur-fitur yang lebih lanjut. -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. +BERDAYAKAN TIM ANDA DENGAN BITWARDEN +Rencana untuk Teams dan Enterprise datang dengan kemampuan bisnis profesional. Beberapa contoh termasuk pemaduan SSO, hosting mandiri, pemaduan direktori dan pembekalan SCIM, kebijakan global, akses API, log kejadian, dan banyak lagi. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Gunakan Bitwarden untuk mengamankan tenaga kerja Anda dan membagikan informasi sensitif kepada rekan kerja. -More reasons to choose Bitwarden: +Alasan lebih lanjut untuk memilih Bitwarden: -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. +Enkripsi Kelas Dunia +Kata sandi dilindungi dengan enkripsi ujung-ke-ujung yang lebih lanjut (AES-256 bit, tanda pagar bergaram, dan PBKDF2 SHA-256) sehingga data Anda tetap aman dan privat. -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. +Audit Pihak Ketiga +Bitwarden secara rutin melakukan audit keamanan yang dilakukan pihak ketiga secara menyeluruh dengan perusahaan keamanan terkemuka. Audit tahunan ini termasuk penilaian sumber kode dan pengujian penembusan antar IP, server, dan aplikasi web Bitwarden. -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 Terdepan +Amankan login Anda dengan pengotentikasi pihak ketiga, kode yang dikirim ke surel, atau pengenal WebAuthn FIDO2 seperti kunci keamanan perangkat keras atau kunci sandi. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Salurkan data ke orang lain secara langsung sembari menjaga keamanan dari ujung-ke-ujung dan membatasi paparan. -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. +Pembuat Bawaan +Buat kata sandi yang panjang, rumit, dan beda serta nama pengguna unik untuk setiap situs yang Anda kunjungi. Padukan dengan nama lain surel untuk privasi lebih lanjut. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Terjemahan Global +Terjemahan Bitwarden hadir dalam lebih dari 60 bahasa, diterjemahkan oleh komunitas global melalui Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Aplikasi Lintas Platform +Amankan dan bagikan data sensitif dalam Brankas Bitwarden Anda dari sebarang peramban, ponsel, atau sistem operasi desktop, dan lebih banyak lagi. -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 mengamankan lebih dari sekedar kata sandi +Solusi pengelolaan pengenal terenkripsi ujung-ke-ujung dari Bitwarden memberdayakan organisasi untuk mengamankan segalanya, termasuk rahasia pengembang dan pengalaman kunci sandi. Kunjungi bitwarden.com untuk mempelajari lebih lanjut tentang Pengelola Rahasia Bitwarden dan passwordless.dev Bitwarden! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Di rumah, di kantor, atau di perjalanan, Bitwarden mengamankan semua kata sandi, kunci sandi, dan informasi sensitif Anda dengan mudah. Sinkronkan dan akses brankas Anda dari beberapa perangkat diff --git a/apps/browser/store/locales/ko/copy.resx b/apps/browser/store/locales/ko/copy.resx index a2fc4e19858..de6ef1c370d 100644 --- a/apps/browser/store/locales/ko/copy.resx +++ b/apps/browser/store/locales/ko/copy.resx @@ -124,48 +124,50 @@ 집에서도, 직장에서도, 이동 중에도 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 접근, 이벤트 로그 등을 포함합니다. +(SSO: 1회 사용자 인증으로 다수의 앱, 웹에 접근, 인증할 수 있는 통합 로그인 솔루션) -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Bitwarden을 사용하여 직원을 보호하고 동료들과 민감한 정보를 안전하게 공유하세요. -More reasons to choose Bitwarden: +Bitwarden을 선택해야 하는 이유 -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. +세계적 수준의 암호화 +고급 엔드 투 엔드 암호화(AES-256 비트, 솔팅된 해시 태그, PBKDF2 SHA-256)로 비밀번호를 보호하여 데이터의 보안과 개인 정보를 유지합니다. -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. +제3자를 통한 보안 감사 +Bitwarden은 저명한 보안 회사와 함께 정기적인 제3자 보안 감사를 수행합니다. 연례 감사에는 소스 코드 평가와 Bitwarden IP, 서버, 웹 애플리케이션에 대한 침투 테스트가 포함됩니다. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +고급 2단계 인증(2FA) +타사 인증 앱, 이메일 코드 또는 하드웨어 보안 키나 패스키와 같은 FIDO2 WebAuthn 자격 증명을 통해 로그인 보안을 강화하세요. -Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Bitwarden의 데이터 전송 방식 +엔드투엔드 암호화를 유지하면서 데이터를 직접 다른 사람에게 전송하여 노출을 최소화합니다. -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 글로벌 커뮤니티를 통해 한국어를 포함한 60개 이상의 언어로 번역되어 있습니다. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +크로스 플랫폼 애플리케이션 +모든 브라우저, 모바일 기기, 데스크톱 OS 등 다양한 환경에서 Bitwarden 보관함 내의 중요한 데이터를 안전하게 관리하고 공유하세요. -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 의 Secrets Manager 및Passwordless.dev에 대해 자세히 알아보려면 Bitwarden.com 을 방문하세요! diff --git a/apps/browser/store/locales/nl/copy.resx b/apps/browser/store/locales/nl/copy.resx index 6e646d5ece8..17720f54f4c 100644 --- a/apps/browser/store/locales/nl/copy.resx +++ b/apps/browser/store/locales/nl/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - wachtwoordbeheerder + Bitwarden Wachtwoordbeheerder - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Thuis, op het werk of onderweg, Bitwarden beveiligt eenvoudig al je wachtwoorden, sleutels en gevoelige informatie. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Erkend als de beste wachtwoordmanager door PCMag, WIRED, The Verge, CNET, G2 en meer! -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. +BEVEILIG JE DIGITALE LEVEN +Beveilig je digitale leven en bescherm je tegen datalekken door unieke, sterke wachtwoorden te genereren en op te slaan voor elke account. Bewaar alles in een end-to-end versleutelde wachtwoordkluis waar alleen jij toegang toe hebt. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +OVERAL EN ALTIJD TOEGANG TOT JE GEGEVENS, OP ELK APPARAAT +Beheer, bewaar, beveilig en deel een onbeperkt aantal wachtwoorden op een onbeperkt aantal apparaten zonder beperkingen. -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. +IEDEREEN ZOU DE MIDDELEN MOETEN HEBBEN OM VEILIG ONLINE TE BLIJVEN +Gebruik Bitwarden gratis, zonder advertenties of verkoop van gegevens. Bitwarden vindt dat iedereen de mogelijkheid moet hebben om veilig online te zijn. Premium abonnementen bieden toegang tot geavanceerde functies. -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. +VERSTERK JE TEAMS MET BITWARDEN +Abonnementen voor Teams en Enterprise worden geleverd met professionele zakelijke functies. Enkele voorbeelden zijn SSO-integratie, zelf hosten, directory-integratie en SCIM provisioning, globaal beleid, API-toegang, gebeurtenislogboeken en meer. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Gebruik Bitwarden om je medewerkers te beveiligen en gevoelige informatie te delen met collega's. -More reasons to choose Bitwarden: +Meer redenen om voor Bitwarden te kiezen: -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. +Encryptie van wereldklasse +Wachtwoorden worden beschermd met geavanceerde end-to-end versleuteling (AES-256 bit, salted hashtag en PBKDF2 SHA-256) zodat je gegevens veilig en privé blijven. -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. +Audits door derde partijen +Bitwarden voert regelmatig uitgebreide beveiligingsaudits uit bij gerenommeerde beveiligingsbedrijven. Deze jaarlijkse audits omvatten broncodebeoordelingen en penetratietests voor Bitwarden IP's, servers en webapplicaties. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Geavanceerde 2FA +Beveilig je login met een authenticator van derden, codes per e-mail of FIDO2 WebAuthn referenties zoals een hardware beveiligingssleutel of passkey. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Verstuur gegevens rechtstreeks naar anderen met behoud van end-to-end versleutelde beveiliging en beperking van blootstelling. -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. +Ingebouwde generator +Maak lange, complexe en duidelijke wachtwoorden en unieke gebruikersnamen voor elke site die je bezoekt. Integreer met e-mail alias providers voor extra privacy. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Wereldwijde vertalingen +Bitwarden vertalingen bestaan voor meer dan 60 talen, vertaald door de wereldwijde gemeenschap via Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Platformoverkoepelende applicaties +Beveilig en deel gevoelige gegevens in je Bitwarden Vault vanuit elke browser, mobiel apparaat of desktop OS, en meer. -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 beveiligt meer dan alleen wachtwoorden +Met de end-to-end versleutelde oplossingen voor referentiebeheer van Bitwarden kunnen organisaties alles beveiligen, inclusief ontwikkelaarsgeheimen en ervaringen met wachtwoorden. Bezoek Bitwarden.com voor meer informatie over Bitwarden Secrets Manager en Bitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Thuis, op het werk of onderweg, Bitwarden beveiligt eenvoudig al je wachtwoorden, sleutels en gevoelige informatie. Synchroniseer en gebruik je kluis op meerdere apparaten diff --git a/apps/browser/store/locales/pt_BR/copy.resx b/apps/browser/store/locales/pt_BR/copy.resx index 24331b3a1ba..f4643115237 100644 --- a/apps/browser/store/locales/pt_BR/copy.resx +++ b/apps/browser/store/locales/pt_BR/copy.resx @@ -121,52 +121,54 @@ Gerenciador de Senhas Bitwarden - Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais. + Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, chaves de acesso e informações sensíveis. Reconhecido como o melhor gerenciador de senhas por "PCMag", 'WIRED', 'The Verge', 'CNET', 'G2', entre outros! PROTEJA A SUA VIDA DIGITAL -Deixe a sua vida digital segura e se proteja contra violações de dados gerando e salvando senhas únicas e fortes para cada conta pessoal. Mantendo tudo isso em um cofre encriptografado de ponta a ponta que apenas você pode acessar. +Deixe a sua vida digital segura e se proteja contra violações de dados gerando e salvando senhas únicas e fortes para cada conta pessoal. Mantendo tudo isso em um cofre criptografado de ponta a ponta em que apenas você pode acessar. ACESSE OS SEUS DADOS, EM QUALQUER LUGAR, HORA E DISPOSITIVO Gerencie, armazene, proteja e compartilhe senhas facilmente entre dispositivos ilimitados e sem restrições. TODOS DEVERIAM TEM FERRAMENTAS PARA SE PROTEGER ONLINE -Utilize Bitwarden de graça sem anuncios ou venda dos seus dados. A Bitwarden acredita que todos deveriam ter a opção de estar seguro online. Planos Premium oferecem recursos mais avançados. +Utilize Bitwarden de graça sem anúncios ou venda dos seus dados. A Bitwarden acredita que todos deveriam ter a opção de estar seguro online. Nossos planos Premium oferecem recursos mais avançados. EMPODERE O SEUS TIMES COM BITWARDEN -Os Planos para times e empresas vem com recursos profissionais para negócios. Alguns exemplos inculuem integração SSO, Auto-hospedagem, integração de diretório e provisionamento SCIM, políticas globais, acesso API, registro de eventos e mais. +Os planos para times e empresas vêm com recursos profissionais para negócios. Alguns exemplos incluem integração SSO, auto-hospedagem, integração de diretório e provisionamento SCIM, políticas globais, acesso API, registro de eventos e mais. -Utilizer Bitwarn para proteger os seus empregados e compartilhar informações sensíveis para os colegas. +Utilize o Bitwarden para proteger os seus colaboradores e compartilhar informações sensíveis com seus colegas. Mais razões para escolher Bitwarden: -Senhas encriptografadas por classe de palavras são protegidas com criptografia de ponta a ponta avançada (AES-256 bit, salted hashtag, and PBKDF2 SHA-256), então os seus dados ficam seguros e privados. +Criptografia mundialmente reconhecida +Suas senhas são protegidas com avançada criptografia ponta a ponta (AES-256, salted hashtag, e PBKDF2 SHA-256) para que os seus dados estejam seguros e privados. Auditoria de Terceiros -Bitwarden regularmente conduz auditorias de terceiros com notáveis empresas de segurança. Essas audições anuais incluem qualificação e penetração do código fonte através de IPs da Bitwarden, servidores e aplicações WEB. +A Bitwarden regularmente conduz auditorias de terceiros com notáveis empresas de segurança. Essas auditorias anuais incluem qualificação do código fonte e testes de invasão contra os IPs da Bitwarden, servidores e aplicações web. 2FA Avançado -Proteja o seu login com um autenticador de doisfatores, códigos de email ou credenciais FIDO2 WebAuthn como chave de segurança por hardware ou chave de acesso. +Proteja o seu login com um autenticador de dois fatores, códigos de email ou credenciais FIDO2 WebAuthn como chave de segurança por hardware ou chave de acesso. Envio Bitwarden -Transmita dados diretamente para outros enquanto mantem segurança de encriptografia ponta a ponta e limitando exposição. +Transmita dados diretamente para outras pessoas enquanto mantém a segurança da criptografia ponta a ponta para limitar sua exposição. Gerador integrado -Crie senhas grandes, complexas e diferentes e nomes de usuário unicos para cada site que visitar. Integre com provedores de email para privacidade adicional +Crie diferentes senhas fortes e complexas e nomes de usuário únicos para cada site que visitar. Integre com provedores de email para privacidade adicional. Tradutores globais -As traduções da Bitwarden estão disponíveis para mais de 60 líguas, traduzidas pela comunidade global através do Crowdin. +As traduções da Bitwarden estão disponíveis para mais de 60 línguas, traduzidas pela comunidade global através do Crowdin. Aplicações Multi-Plataforma -Proteja e compartilhe conteúdo sensível dentro do Vault da Bitwarden de qualquer navegador, dispositivo móvel, ou desktop OS, entre outros. +Proteja e compartilhe conteúdo confidencial dentro do Vault da Bitwarden de qualquer navegador, dispositivo móvel, desktop, entre outros. -Bitwarden protege mais do que apenas soluções em gerenciamento de credenciais de senhas encriptografadas ponta a ponta, Bitwarden empodera organizações para proteger qualquer coisa, incluindo segredos de desenvolvedor e chaves de acesso. -Visite Bitwarden.com para aprender mais sobre Bitwarden Secrets Manager e Bitwarden Passwordless.dev! +Bitwarden protege mais do que apenas senhas +Com nossas soluções em gerenciamento de credenciais criptografadas ponta a ponta, a Bitwarden empodera organizações para proteger qualquer informação, incluindo senhas de desenvolvedor e chaves de acesso. Visite Bitwarden.com para aprender mais sobre Bitwarden Secrets Manager e Bitwarden Passwordless.dev! + - Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais. + Em casa, no trabalho ou em qualquer lugar, a Bitwarden protege facilmente todas as suas senhas, chaves de acesso e informações sensíveis. Sincronize e acesse o seu cofre através de múltiplos dispositivos diff --git a/apps/browser/store/locales/sr/copy.resx b/apps/browser/store/locales/sr/copy.resx index a7657997a92..2e737523856 100644 --- a/apps/browser/store/locales/sr/copy.resx +++ b/apps/browser/store/locales/sr/copy.resx @@ -124,48 +124,48 @@ Било где, 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 да би сте обезбедили вашу радну снагу и делили осетљиве информације са колегама. -More reasons to choose Bitwarden: +Додатни разлози да изаберете Bitwarden: -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. +Енкрипција светске класе +Лозинке су заштићене са напредном потпуном енкрипцијом (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) тако да ваши подаци остају сигурни и приватни. -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. +Ревизије треће стране +Bitwarden редовно спроводи опсежне безбедносне ревизије трећих страна заједно са препознатим безбедносним фирмама. Ове годишње ревизије укључују процене изворног кода и тестирање пробојности Bitwarden-ових ИП адреса, сервера и веб апликација. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Напредна двофакторска аутентификација +Обезбедите ваше пријаве са аутентификатором треће стране, кодовима послатим на е-пошту, или 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 преводи постоје за више од 60 језика, преведени од стране глобалне заједнице уз помоћ Crowdin-а. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Апликације за разне платформе +Обезбедите и делите осетљиве податке у оквиру Bitwarden трезора из било којег прегледача, мобилног уређаја или оперативног система и више. -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.com да би сте сазнали више о Bitwarden руководиоцу тајнама и Bitwarden Passwordless.dev! diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index ea98321a499..0f73ccd6619 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -121,7 +121,7 @@ Bitwarden 密码管理器 - 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 + 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳的密码管理器! @@ -135,11 +135,12 @@ 每个人都应该拥有的保持在线安全的工具 使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了对高级功能的访问。 -使用 BITWARDEN 为您的团队提供支持 +使用 Bitwarden 为您的团队提供支持 团队计划和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成,以及 SCIM 配置、全局策略、API 访问、事件日志等。 使用 Bitwarden 保护您的劳动成果,并与同事共享敏感信息。 + 选择 Bitwarden 的更多理由: 世界级加密 @@ -164,10 +165,10 @@ Bitwarden 的翻译涵盖 60 多种语言,由全球社区使用 Crowdin 翻译 从任何浏览器、移动设备或桌面操作系统中安全地访问和共享 Bitwarden 密码库中的敏感数据。 Bitwarden 保护的不仅仅是密码 -Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! +Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 进一步了解 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev! - 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 + 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 在多个设备间同步和访问您的密码库 diff --git a/apps/browser/store/windows/AppxManifest.xml b/apps/browser/store/windows/AppxManifest.xml index df02ea085cf..9765506a558 100644 --- a/apps/browser/store/windows/AppxManifest.xml +++ b/apps/browser/store/windows/AppxManifest.xml @@ -12,7 +12,7 @@ Bitwarden Password Manager - 8bit Solutions LLC + Bitwarden Inc Assets/icon_50.png diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index 2e8f9c9f817..d0ec8025c66 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -5,6 +5,7 @@ config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", + "../../libs/key-management/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index e27d80e595a..c1ef1443acc 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -34,8 +34,14 @@ "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault": ["../../libs/vault/src"] }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ], "useDefineForClassFields": false }, "angularCompilerOptions": { diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 5436e51011a..6ba74d7df43 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -7,58 +7,14 @@ const { AngularWebpackPlugin } = require("@ngtools/webpack"); const TerserPlugin = require("terser-webpack-plugin"); const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin"); const configurator = require("./config/config"); +const manifest = require("./webpack/manifest"); if (process.env.NODE_ENV == null) { process.env.NODE_ENV = "development"; } const ENV = (process.env.ENV = process.env.NODE_ENV); const manifestVersion = process.env.MANIFEST_VERSION == 3 ? 3 : 2; -const browser = process.env.BROWSER; - -function modifyManifestV3(buffer) { - if (manifestVersion === 2 || !browser) { - return buffer; - } - - const manifest = JSON.parse(buffer.toString()); - - if (browser === "chrome") { - // Remove unsupported properties - delete manifest.applications; - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - - return JSON.stringify(manifest, null, 2); - } - - // Update the background script reference to be an event page - const backgroundScript = manifest.background.service_worker; - delete manifest.background.service_worker; - manifest.background.scripts = [backgroundScript]; - - // Remove unsupported properties - delete manifest.content_security_policy.sandbox; - delete manifest.sandbox; - delete manifest.applications; - - manifest.permissions = manifest.permissions.filter((permission) => permission !== "offscreen"); - - if (browser === "safari") { - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - delete manifest.optional_permissions; - manifest.permissions.push("nativeMessaging"); - } - - if (browser === "firefox") { - delete manifest.storage; - manifest.optional_permissions = manifest.optional_permissions.filter( - (permission) => permission !== "privacy", - ); - } - - return JSON.stringify(manifest, null, 2); -} +const browser = process.env.BROWSER ?? "chrome"; console.log(`Building Manifest Version ${manifestVersion} app`); @@ -142,9 +98,10 @@ const requiredPlugins = [ const plugins = [ new HtmlWebpackPlugin({ - template: "./src/popup/index.html", + template: "./src/popup/index.ejs", filename: "popup/index.html", chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"], + browser: browser, }), new HtmlWebpackPlugin({ template: "./src/autofill/notification/bar.html", @@ -178,13 +135,11 @@ const plugins = [ }), new CopyWebpackPlugin({ patterns: [ - manifestVersion == 3 - ? { - from: "./src/manifest.v3.json", - to: "manifest.json", - transform: (content) => modifyManifestV3(content), - } - : "./src/manifest.json", + { + from: manifestVersion == 3 ? "./src/manifest.v3.json" : "./src/manifest.json", + to: "manifest.json", + transform: manifest.transform(browser), + }, { from: "./src/managed_schema.json", to: "managed_schema.json" }, { from: "./src/_locales", to: "_locales" }, { from: "./src/images", to: "images" }, diff --git a/apps/browser/webpack/manifest.js b/apps/browser/webpack/manifest.js new file mode 100644 index 00000000000..cf8296707bb --- /dev/null +++ b/apps/browser/webpack/manifest.js @@ -0,0 +1,72 @@ +/** + * Transform the manifest template into a browser specific manifest. + * + * We support a simple browser prefix to the manifest keys. Example: + * + * ```json + * { + * "name": "Default name", + * "__chrome__name": "Chrome override" + * } + * ``` + * + * Will result in the following manifest: + * + * ```json + * { + * "name": "Chrome override" + * } + * ``` + * + * for Chrome. + */ +function transform(browser) { + return (buffer) => { + let manifest = JSON.parse(buffer.toString()); + + manifest = transformPrefixes(manifest, browser); + + return JSON.stringify(manifest, null, 2); + }; +} + +const browsers = ["chrome", "edge", "firefox", "opera", "safari"]; + +/** + * Flatten the browser prefixes in the manifest. + * + * - Removes unrelated browser prefixes. + * - A null value deletes the non prefixed key. + */ +function transformPrefixes(manifest, browser) { + const prefix = `__${browser}__`; + + function transformObject(obj) { + return Object.keys(obj).reduce((acc, key) => { + // Determine if we need to recurse into the object. + const nested = typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key]); + + if (key.startsWith(prefix)) { + const newKey = key.slice(prefix.length); + + // Null values are used to remove keys. + if (obj[key] == null) { + delete acc[newKey]; + return acc; + } + + acc[newKey] = nested ? transformObject(obj[key]) : obj[key]; + } else if (!browsers.some((b) => key.startsWith(`__${b}__`))) { + acc[key] = nested ? transformObject(obj[key]) : obj[key]; + } + + return acc; + }, {}); + } + + return transformObject(manifest); +} + +module.exports = { + transform, +}; diff --git a/apps/cli/README.md b/apps/cli/README.md index e00b9a345d2..d39c0e39c8f 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -7,8 +7,6 @@ The Bitwarden CLI is a powerful, full-featured command-line interface (CLI) tool to access and manage a Bitwarden vault. The CLI is written with TypeScript and Node.js and can be run on Windows, macOS, and Linux distributions. -![CLI](https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/cli-macos.png "CLI") - ## Developer Documentation Please refer to the [CLI section](https://contributing.bitwarden.com/getting-started/clients/cli/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. diff --git a/apps/cli/package.json b/apps/cli/package.json index 8f0269c2ce4..d1d8ac76ec4 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": "2024.11.0", + "version": "2025.1.1", "keywords": [ "bitwarden", "password", @@ -18,14 +18,14 @@ "license": "SEE LICENSE IN LICENSE.txt", "scripts": { "clean": "rimraf dist", - "build:oss": "webpack", + "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:oss:debug": "npm run build:oss && node --inspect ./build/bw.js", "build:oss:watch": "webpack --watch", "build:oss:prod": "cross-env NODE_ENV=production webpack", "build:oss:prod:watch": "cross-env NODE_ENV=production webpack --watch", "debug": "node --inspect ./build/bw.js", "publish:npm": "npm run build:oss:prod && npm publish --access public", - "build:bit": "webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", + "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", "build:bit:debug": "npm run build:bit && node --inspect ./build/bw.js", "build:bit:watch": "webpack --watch -c ../../bitwarden_license/bit-cli/webpack.config.js", "build:bit:prod": "cross-env NODE_ENV=production npm run build:bit", @@ -80,7 +80,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.60", + "tldts": "6.1.71", "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 0761dfef18e..5f5f58163e3 100644 --- a/apps/cli/src/admin-console/commands/confirm.command.ts +++ b/apps/cli/src/admin-console/commands/confirm.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserApiService, OrganizationUserConfirmRequest, diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index bbd4241e21e..e26d073326e 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -32,6 +34,8 @@ export class ShareCommand { if (req == null || req.length === 0) { return Response.badRequest("You must provide at least one collection id for this item."); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/admin-console/models/request/organization-collection.request.ts b/apps/cli/src/admin-console/models/request/organization-collection.request.ts index 1bb7a24ce77..b5f796afe2d 100644 --- a/apps/cli/src/admin-console/models/request/organization-collection.request.ts +++ b/apps/cli/src/admin-console/models/request/organization-collection.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { SelectionReadOnly } from "../selection-read-only"; diff --git a/apps/cli/src/admin-console/models/response/organization-user.response.ts b/apps/cli/src/admin-console/models/response/organization-user.response.ts index 78fef4e42b6..c6c32b278de 100644 --- a/apps/cli/src/admin-console/models/response/organization-user.response.ts +++ b/apps/cli/src/admin-console/models/response/organization-user.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserStatusType, OrganizationUserType, diff --git a/apps/cli/src/auth/commands/lock.command.ts b/apps/cli/src/auth/commands/lock.command.ts index 912d4a74917..809d3c6a881 100644 --- a/apps/cli/src/auth/commands/lock.command.ts +++ b/apps/cli/src/auth/commands/lock.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { Response } from "../../models/response"; diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 57477ee2bc8..359ed08ca99 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as http from "http"; import { OptionValues } from "commander"; @@ -17,7 +19,6 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs import { PolicyService } 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 { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -37,7 +38,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { NodeUtils } from "@bitwarden/node/node-utils"; import { Response } from "../../models/response"; @@ -164,6 +165,8 @@ export class LoginCommand { if (options.method != null) { twoFactorMethod = parseInt(options.method, null); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } @@ -239,6 +242,8 @@ export class LoginCommand { if (twoFactorMethod != null) { try { selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } diff --git a/apps/cli/src/auth/commands/logout.command.ts b/apps/cli/src/auth/commands/logout.command.ts index f3f5b21a09a..d7690b3503f 100644 --- a/apps/cli/src/auth/commands/logout.command.ts +++ b/apps/cli/src/auth/commands/logout.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/apps/cli/src/auth/commands/unlock.command.ts b/apps/cli/src/auth/commands/unlock.command.ts index 3389d022e5e..f33a9cf347a 100644 --- a/apps/cli/src/auth/commands/unlock.command.ts +++ b/apps/cli/src/auth/commands/unlock.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/apps/cli/src/base-program.ts b/apps/cli/src/base-program.ts index 29a895e5166..14c930b804a 100644 --- a/apps/cli/src/base-program.ts +++ b/apps/cli/src/base-program.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as chalk from "chalk"; import { firstValueFrom, map } from "rxjs"; diff --git a/apps/cli/src/commands/completion.command.ts b/apps/cli/src/commands/completion.command.ts index 49dc3e9a13f..918150a8e6b 100644 --- a/apps/cli/src/commands/completion.command.ts +++ b/apps/cli/src/commands/completion.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { program, OptionValues, Command } from "commander"; import { Response } from "../models/response"; diff --git a/apps/cli/src/commands/convert-to-key-connector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts index 0dbdbb43250..8c8a3fc9a10 100644 --- a/apps/cli/src/commands/convert-to-key-connector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -1,3 +1,5 @@ +// 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"; diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index f819875063d..6a7cda2ac91 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -1,5 +1,6 @@ -import * as fet from "node-fetch"; - +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/platform/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"; @@ -8,16 +9,36 @@ import { Response } from "../models/response"; import { FileResponse } from "../models/response/file.response"; import { CliUtils } from "../utils"; +/** + * Used to download and save attachments + */ export abstract class DownloadCommand { - constructor(protected encryptService: EncryptService) {} + /** + * @param encryptService - Needed for decryption of the retrieved attachment + * @param apiService - Needed to override the existing nativeFetch which is available as of Node 18, to support proxies + */ + constructor( + protected encryptService: EncryptService, + protected apiService: ApiService, + ) {} + /** + * 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 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, output?: string, ) { - const response = await fet.default(new fet.Request(url, { headers: { cache: "no-cache" } })); + const response = await this.apiService.nativeFetch( + new Request(url, { headers: { cache: "no-cache" } }), + ); if (response.status !== 200) { return Response.error( "A " + response.status + " error occurred while downloading the attachment.", diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 2e97f683801..8efb414f5b2 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { CollectionRequest } from "@bitwarden/admin-console/common"; @@ -22,6 +24,8 @@ import { CipherResponse } from "../vault/models/cipher.response"; import { FolderResponse } from "../vault/models/folder.response"; export class EditCommand { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private folderService: FolderService, @@ -53,6 +57,8 @@ export class EditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } @@ -119,12 +125,12 @@ export class EditCommand { cipher.collectionIds = req; try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + await this.cipherService.getKeyForCipherKeyDecryption( + updatedCipher, + await firstValueFrom(this.activeUserId$), + ), ); const res = new CipherResponse(decCipher); return Response.success(res); @@ -134,7 +140,8 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const folder = await this.folderService.getFromState(id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); } @@ -142,12 +149,11 @@ export class EditCommand { let folderView = await folder.decrypt(); folderView = FolderExport.toView(req, folderView); - const activeUserId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const encFolder = await this.folderService.encrypt(folderView, userKey); try { - await this.folderApiService.save(encFolder); - const updatedFolder = await this.folderService.get(folder.id); + await this.folderApiService.save(encFolder, activeUserId); + const updatedFolder = await this.folderService.get(folder.id, activeUserId); const decFolder = await updatedFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index fc014534e03..a1fec7a7472 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; @@ -21,7 +23,6 @@ import { LoginExport } from "@bitwarden/common/models/export/login.export"; import { SecureNoteExport } from "@bitwarden/common/models/export/secure-note.export"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; 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"; @@ -50,6 +51,8 @@ import { FolderResponse } from "../vault/models/folder.response"; import { DownloadCommand } from "./download.command"; export class GetCommand extends DownloadCommand { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private folderService: FolderService, @@ -58,15 +61,14 @@ export class GetCommand extends DownloadCommand { private auditService: AuditService, private keyService: KeyService, encryptService: EncryptService, - private stateService: StateService, private searchService: SearchService, - private apiService: ApiService, + protected apiService: ApiService, private organizationService: OrganizationService, private eventCollectionService: EventCollectionService, private accountProfileService: BillingAccountProfileStateService, private accountService: AccountService, ) { - super(encryptService); + super(encryptService, apiService); } async run(object: string, id: string, cmdOptions: Record): Promise { @@ -113,10 +115,8 @@ export class GetCommand extends DownloadCommand { let decCipher: CipherView = null; if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); if (cipher != null) { + const activeUserId = await firstValueFrom(this.activeUserId$); decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -152,11 +152,9 @@ export class GetCommand extends DownloadCommand { } } - // 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.eventCollectionService.collect( + await this.eventCollectionService.collect( EventType.Cipher_ClientViewed, - id, + decCipher.id, true, decCipher.organizationId, ); @@ -262,8 +260,9 @@ export class GetCommand extends DownloadCommand { return Response.error("Couldn't generate TOTP code."); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (!canAccessPremium) { const originalCipher = await this.cipherService.get(cipher.id); @@ -347,8 +346,9 @@ export class GetCommand extends DownloadCommand { return Response.multipleResults(attachments.map((a) => a.id)); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (!canAccessPremium) { const originalCipher = await this.cipherService.get(cipher.id); @@ -383,13 +383,14 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; + const activeUserId = await firstValueFrom(this.activeUserId$); if (Utils.isGuid(id)) { - const folder = await this.folderService.getFromState(id); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { decFolder = await folder.decrypt(); } } else if (id.trim() !== "") { - let folders = await this.folderService.getAllDecryptedFromState(); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); folders = CliUtils.searchFolders(folders, id); if (folders.length > 1) { return Response.multipleResults(folders.map((f) => f.id)); @@ -551,9 +552,7 @@ export class GetCommand extends DownloadCommand { private async getFingerprint(id: string) { let fingerprint: string[] = null; if (id === "me") { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); } else if (Utils.isGuid(id)) { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 9cb36f71496..92da86b696a 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationUserApiService, @@ -12,6 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -38,6 +39,7 @@ export class ListCommand { private organizationUserApiService: OrganizationUserApiService, private apiService: ApiService, private eventCollectionService: EventCollectionService, + private accountService: AccountService, ) {} async run(object: string, cmdOptions: Record): Promise { @@ -135,7 +137,10 @@ export class ListCommand { } private async listFolders(options: Options) { - let folders = await this.folderService.getAllDecryptedFromState(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); if (options.search != null && options.search.trim() !== "") { folders = CliUtils.searchFolders(folders, options.search); diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index 3fe8be620c2..f7fc8541a5f 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/apps/cli/src/commands/update.command.ts b/apps/cli/src/commands/update.command.ts index b70c4abcc57..185b272d480 100644 --- a/apps/cli/src/commands/update.command.ts +++ b/apps/cli/src/commands/update.command.ts @@ -1,5 +1,6 @@ -import * as fetch from "node-fetch"; - +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Response } from "../models/response"; @@ -12,12 +13,15 @@ const UPDATE_COMMAND = "npm install -g @bitwarden/cli"; export class UpdateCommand { inPkg = false; - constructor(private platformUtilsService: PlatformUtilsService) { + constructor( + private platformUtilsService: PlatformUtilsService, + protected apiService: ApiService, + ) { this.inPkg = !!(process as any).pkg; } async run(): Promise { - const response = await fetch.default(CLIENTS_RELEASE_LIST_ENDPOINT); + const response = await this.apiService.nativeFetch(new Request(CLIENTS_RELEASE_LIST_ENDPOINT)); if (response.status !== 200) { return Response.error("Error contacting update API: " + response.status); } diff --git a/apps/cli/src/key-management/cli-biometrics-service.ts b/apps/cli/src/key-management/cli-biometrics-service.ts new file mode 100644 index 00000000000..bda8fe82895 --- /dev/null +++ b/apps/cli/src/key-management/cli-biometrics-service.ts @@ -0,0 +1,27 @@ +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; + +export class CliBiometricsService extends BiometricsService { + async authenticateWithBiometrics(): Promise { + return false; + } + + async getBiometricsStatus(): Promise { + return BiometricsStatus.PlatformUnsupported; + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return null; + } + + async getBiometricsStatusForUser(userId: UserId): Promise { + return BiometricsStatus.PlatformUnsupported; + } + + async getShouldAutopromptNow(): Promise { + return false; + } + + async setShouldAutopromptNow(value: boolean): Promise {} +} diff --git a/apps/cli/src/models/response.ts b/apps/cli/src/models/response.ts index cbeb9f17273..76d9509226d 100644 --- a/apps/cli/src/models/response.ts +++ b/apps/cli/src/models/response.ts @@ -1,19 +1,36 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; + import { BaseResponse } from "./response/base.response"; +function getErrorMessage(error: unknown): string { + if (typeof error === "string") { + return error; + } + if (error instanceof ErrorResponse) { + const message = error.getSingleMessage(); + if (message) { + return message; + } + } + if (error instanceof Error) { + return String(error); + } + if (error) { + const errorWithMessage: { message?: unknown } = error; // To placate TypeScript. + if (errorWithMessage.message && typeof errorWithMessage.message === "string") { + return errorWithMessage.message; + } + } + return JSON.stringify(error); +} + export class Response { static error(error: any, data?: any): Response { const res = new Response(); res.success = false; - if (typeof error === "string") { - res.message = error; - } else { - res.message = - error.message != null - ? error.message - : error.toString() === "[object Object]" - ? JSON.stringify(error) - : error.toString(); - } + res.message = getErrorMessage(error); res.data = data; return res; } diff --git a/apps/cli/src/models/response/login.response.ts b/apps/cli/src/models/response/login.response.ts index 1f339743fa8..092102717ce 100644 --- a/apps/cli/src/models/response/login.response.ts +++ b/apps/cli/src/models/response/login.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LoginExport } from "@bitwarden/common/models/export/login.export"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; diff --git a/apps/cli/src/models/response/message.response.ts b/apps/cli/src/models/response/message.response.ts index 1440f97b646..dcbe3b92216 100644 --- a/apps/cli/src/models/response/message.response.ts +++ b/apps/cli/src/models/response/message.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "./base.response"; export class MessageResponse implements BaseResponse { diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index a25357f6f69..be476d19814 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as koaMulter from "@koa/multer"; import * as koaRouter from "@koa/router"; import * as koa from "koa"; @@ -58,7 +60,6 @@ export class OssServeConfigurator { this.serviceContainer.auditService, this.serviceContainer.keyService, this.serviceContainer.encryptService, - this.serviceContainer.stateService, this.serviceContainer.searchService, this.serviceContainer.apiService, this.serviceContainer.organizationService, @@ -75,6 +76,7 @@ export class OssServeConfigurator { this.serviceContainer.organizationUserApiService, this.serviceContainer.apiService, this.serviceContainer.eventCollectionService, + this.serviceContainer.accountService, ); this.createCommand = new CreateCommand( this.serviceContainer.cipherService, @@ -114,6 +116,7 @@ export class OssServeConfigurator { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.cipherAuthorizationService, + this.serviceContainer.accountService, ); this.confirmCommand = new ConfirmCommand( this.serviceContainer.apiService, @@ -146,6 +149,7 @@ export class OssServeConfigurator { this.serviceContainer.environmentService, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); this.sendDeleteCommand = new SendDeleteCommand( this.serviceContainer.sendService, @@ -156,12 +160,14 @@ export class OssServeConfigurator { this.serviceContainer.environmentService, this.serviceContainer.searchService, this.serviceContainer.encryptService, + this.serviceContainer.apiService, ); this.sendEditCommand = new SendEditCommand( this.serviceContainer.sendService, this.sendGetCommand, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); this.sendListCommand = new SendListCommand( this.serviceContainer.sendService, diff --git a/apps/cli/src/platform/commands/config.command.ts b/apps/cli/src/platform/commands/config.command.ts index cd94e8af9c4..b35ee7824ba 100644 --- a/apps/cli/src/platform/commands/config.command.ts +++ b/apps/cli/src/platform/commands/config.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OptionValues } from "commander"; import { firstValueFrom } from "rxjs"; diff --git a/apps/cli/src/platform/flags.ts b/apps/cli/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/cli/src/platform/flags.ts +++ b/apps/cli/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { 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 24bceec389c..87b1a79435c 100644 --- a/apps/cli/src/platform/services/cli-platform-utils.service.ts +++ b/apps/cli/src/platform/services/cli-platform-utils.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as child_process from "child_process"; import { ClientType, DeviceType } from "@bitwarden/common/enums"; diff --git a/apps/cli/src/platform/services/console-log.service.ts b/apps/cli/src/platform/services/console-log.service.ts index 5bdc0b40152..922f9696be0 100644 --- a/apps/cli/src/platform/services/console-log.service.ts +++ b/apps/cli/src/platform/services/console-log.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogLevelType } from "@bitwarden/common/platform/enums/log-level-type.enum"; import { ConsoleLogService as BaseConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; diff --git a/apps/cli/src/platform/services/lowdb-storage.service.ts b/apps/cli/src/platform/services/lowdb-storage.service.ts index 3cbaeeafc02..f4542236395 100644 --- a/apps/cli/src/platform/services/lowdb-storage.service.ts +++ b/apps/cli/src/platform/services/lowdb-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; diff --git a/apps/cli/src/platform/services/node-api.service.ts b/apps/cli/src/platform/services/node-api.service.ts index c480d9d1aff..f4d1d30e8b1 100644 --- a/apps/cli/src/platform/services/node-api.service.ts +++ b/apps/cli/src/platform/services/node-api.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as FormData from "form-data"; import { HttpsProxyAgent } from "https-proxy-agent"; import * as fe from "node-fetch"; 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 2ab18b6c463..2807509e428 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { throwError } from "rxjs"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -85,6 +87,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } return Utils.fromBufferToB64(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Decrypt error."); return null; @@ -102,6 +106,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Session key is invalid."); } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 7e0f75de4e0..01844986834 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; +import { firstValueFrom } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -102,6 +105,14 @@ export class Program extends BaseProgram { writeLn("", true); }); + program + .command("sdk-version") + .description("Print the SDK version.") + .action(async () => { + const sdkVersion = await firstValueFrom(this.serviceContainer.sdkService.version$); + writeLn(sdkVersion, true); + }); + program .command("login [email] [password]") .description("Log into a user account.") @@ -417,7 +428,10 @@ export class Program extends BaseProgram { writeLn("", true); }) .action(async () => { - const command = new UpdateCommand(this.serviceContainer.platformUtilsService); + const command = new UpdateCommand( + this.serviceContainer.platformUtilsService, + this.serviceContainer.apiService, + ); const response = await command.run(); this.processResponse(response); }); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index ae627e82e75..f57db9909d6 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; @@ -33,14 +35,12 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; 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"; import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; -import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; @@ -149,6 +149,8 @@ import { ImportServiceAbstraction, } from "@bitwarden/importer/core"; import { + DefaultKdfConfigService, + KdfConfigService, DefaultKeyService as KeyService, BiometricStateService, DefaultBiometricStateService, @@ -163,6 +165,7 @@ import { VaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; +import { CliBiometricsService } from "../key-management/cli-biometrics-service"; import { flagEnabled } from "../platform/flags"; import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service"; import { ConsoleLogService } from "../platform/services/console-log.service"; @@ -260,7 +263,7 @@ export class ServiceContainer { billingAccountProfileStateService: BillingAccountProfileStateService; providerApiService: ProviderApiServiceAbstraction; userAutoUnlockKeyService: UserAutoUnlockKeyService; - kdfConfigService: KdfConfigServiceAbstraction; + kdfConfigService: KdfConfigService; taskSchedulerService: TaskSchedulerService; sdkService: SdkService; cipherAuthorizationService: CipherAuthorizationService; @@ -407,7 +410,7 @@ export class ServiceContainer { this.logService, ); - this.kdfConfigService = new KdfConfigService(this.stateProvider); + this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.pinService = new PinService( this.accountService, @@ -481,9 +484,31 @@ export class ServiceContainer { this.containerService = new ContainerService(this.keyService, this.encryptService); - this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - this.fileUploadService = new FileUploadService(this.logService); + this.authService = new AuthService( + this.accountService, + this.messagingService, + this.keyService, + this.apiService, + this.stateService, + this.tokenService, + ); + + this.configService = new DefaultConfigService( + this.configApiService, + this.environmentService, + this.logService, + this.stateProvider, + this.authService, + ); + + this.domainSettingsService = new DefaultDomainSettingsService( + this.stateProvider, + this.configService, + ); + + this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.sendStateProvider = new SendStateProvider(this.stateProvider); @@ -548,7 +573,6 @@ export class ServiceContainer { this.accountService, this.kdfConfigService, this.keyService, - this.apiService, customUserAgent, ); @@ -574,28 +598,11 @@ export class ServiceContainer { this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( this.stateProvider, - ); - - this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService); - - this.authService = new AuthService( - this.accountService, - this.messagingService, - this.keyService, + this.platformUtilsService, this.apiService, - this.stateService, - this.tokenService, ); - this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - - this.configService = new DefaultConfigService( - this.configApiService, - this.environmentService, - this.logService, - this.stateProvider, - this.authService, - ); + this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService); this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustService = new DeviceTrustService( @@ -687,12 +694,12 @@ export class ServiceContainer { this.userVerificationApiService, this.userDecryptionOptionsService, this.pinService, - this.logService, - this.vaultTimeoutSettingsService, - this.platformUtilsService, this.kdfConfigService, + new CliBiometricsService(), ); + const biometricService = new CliBiometricsService(); + this.vaultTimeoutService = new VaultTimeoutService( this.accountService, this.masterPasswordService, @@ -708,6 +715,7 @@ export class ServiceContainer { this.stateEventRunnerService, this.taskSchedulerService, this.logService, + biometricService, lockedCallback, undefined, ); @@ -862,19 +870,5 @@ export class ServiceContainer { } this.inited = true; - - if (flagEnabled("sdk")) { - // Warn if the SDK for some reason can't be initialized - let supported = false; - try { - supported = await firstValueFrom(this.sdkService.supported$); - } catch (e) { - // Do nothing. - } - - if (!supported) { - this.sdkService.failedToInitialize("cli").catch((e) => this.logService.error(e)); - } - } } } diff --git a/apps/cli/src/service-container/tsconfig.json b/apps/cli/src/service-container/tsconfig.json deleted file mode 100644 index ee24d230ae2..00000000000 --- a/apps/cli/src/service-container/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "strictNullChecks": true, - "strictPropertyInitialization": true - } -} diff --git a/apps/cli/src/tools/export.command.ts b/apps/cli/src/tools/export.command.ts index 51c96908909..b2d9d0151a9 100644 --- a/apps/cli/src/tools/export.command.ts +++ b/apps/cli/src/tools/export.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OptionValues } from "commander"; import * as inquirer from "inquirer"; diff --git a/apps/cli/src/tools/generate.command.ts b/apps/cli/src/tools/generate.command.ts index 2829bc51414..64c6118a0c6 100644 --- a/apps/cli/src/tools/generate.command.ts +++ b/apps/cli/src/tools/generate.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DefaultPasswordGenerationOptions, @@ -33,7 +35,7 @@ export class GenerateCommand { includeNumber: normalizedOptions.includeNumber, minNumber: normalizedOptions.minNumber, minSpecial: normalizedOptions.minSpecial, - ambiguous: normalizedOptions.ambiguous, + ambiguous: !normalizedOptions.ambiguous, }; const enforcedOptions = (await this.stateService.getIsAuthenticated()) diff --git a/apps/cli/src/tools/import.command.ts b/apps/cli/src/tools/import.command.ts index 1cb3ac19f6b..a71c9177d29 100644 --- a/apps/cli/src/tools/import.command.ts +++ b/apps/cli/src/tools/import.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OptionValues } from "commander"; import * as inquirer from "inquirer"; @@ -27,7 +29,7 @@ export class ImportCommand { ); } - if (!organization.canAccessImportExport) { + if (!organization.canAccessImport) { return Response.badRequest( "You are not authorized to import into the provided organization.", ); diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index a3f4b5c086f..a9264c50126 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -1,8 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, switchMap } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; @@ -21,6 +24,7 @@ export class SendCreateCommand { private environmentService: EnvironmentService, private sendApiService: SendApiService, private accountProfileService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async run(requestJson: any, cmdOptions: Record) { @@ -45,6 +49,8 @@ export class SendCreateCommand { if (req == null) { throw new Error("Null request"); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } @@ -76,6 +82,10 @@ export class SendCreateCommand { req.key = null; req.maxAccessCount = maxAccessCount; + const hasPremium$ = this.accountService.activeAccount$.pipe( + switchMap(({ id }) => this.accountProfileService.hasPremiumFromAnySource$(id)), + ); + switch (req.type) { case SendType.File: if (process.env.BW_SERVE === "true") { @@ -84,7 +94,7 @@ export class SendCreateCommand { ); } - if (!(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$))) { + if (!(await firstValueFrom(hasPremium$))) { return Response.error("Premium status is required to use this feature."); } diff --git a/apps/cli/src/tools/send/commands/edit.command.ts b/apps/cli/src/tools/send/commands/edit.command.ts index 315b2411d8a..ed719b58311 100644 --- a/apps/cli/src/tools/send/commands/edit.command.ts +++ b/apps/cli/src/tools/send/commands/edit.command.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -17,6 +20,7 @@ export class SendEditCommand { private getCommand: SendGetCommand, private sendApiService: SendApiService, private accountProfileService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async run(requestJson: string, cmdOptions: Record): Promise { @@ -37,6 +41,8 @@ export class SendEditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = SendResponse.fromJson(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } @@ -59,8 +65,9 @@ export class SendEditCommand { return Response.badRequest("Cannot change a Send's type"); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (send.type === SendType.File && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); diff --git a/apps/cli/src/tools/send/commands/get.command.ts b/apps/cli/src/tools/send/commands/get.command.ts index 057a1c27f33..0e650c8503d 100644 --- a/apps/cli/src/tools/send/commands/get.command.ts +++ b/apps/cli/src/tools/send/commands/get.command.ts @@ -1,6 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OptionValues } from "commander"; import { firstValueFrom } from "rxjs"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -18,8 +21,9 @@ export class SendGetCommand extends DownloadCommand { private environmentService: EnvironmentService, private searchService: SearchService, encryptService: EncryptService, + apiService: ApiService, ) { - super(encryptService); + super(encryptService, apiService); } async run(id: string, options: OptionValues) { diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index 76fdc3fca6f..41a6681af55 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -1,7 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OptionValues } from "commander"; import * as inquirer from "inquirer"; import { firstValueFrom } from "rxjs"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -33,8 +36,9 @@ export class SendReceiveCommand extends DownloadCommand { private platformUtilsService: PlatformUtilsService, private environmentService: EnvironmentService, private sendApiService: SendApiService, + apiService: ApiService, ) { - super(encryptService); + super(encryptService, apiService); } async run(url: string, options: OptionValues): Promise { @@ -43,6 +47,8 @@ export class SendReceiveCommand extends DownloadCommand { let urlObject: URL; try { urlObject = new URL(url); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Failed to parse the provided Send url"); } diff --git a/apps/cli/src/tools/send/commands/remove-password.command.ts b/apps/cli/src/tools/send/commands/remove-password.command.ts index 2613004a8ce..4f7add366be 100644 --- a/apps/cli/src/tools/send/commands/remove-password.command.ts +++ b/apps/cli/src/tools/send/commands/remove-password.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; diff --git a/apps/cli/src/tools/send/models/send-access.response.ts b/apps/cli/src/tools/send/models/send-access.response.ts index 5e9b90e5a5f..07877bfb548 100644 --- a/apps/cli/src/tools/send/models/send-access.response.ts +++ b/apps/cli/src/tools/send/models/send-access.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view"; diff --git a/apps/cli/src/tools/send/models/send-file.response.ts b/apps/cli/src/tools/send/models/send-file.response.ts index 9c66e898066..5bc32ebd4d1 100644 --- a/apps/cli/src/tools/send/models/send-file.response.ts +++ b/apps/cli/src/tools/send/models/send-file.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendFileView } from "@bitwarden/common/tools/send/models/view/send-file.view"; export class SendFileResponse { diff --git a/apps/cli/src/tools/send/models/send-text.response.ts b/apps/cli/src/tools/send/models/send-text.response.ts index a5ad7085ecc..8db236deb62 100644 --- a/apps/cli/src/tools/send/models/send-text.response.ts +++ b/apps/cli/src/tools/send/models/send-text.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendTextView } from "@bitwarden/common/tools/send/models/view/send-text.view"; export class SendTextResponse { diff --git a/apps/cli/src/tools/send/models/send.response.ts b/apps/cli/src/tools/send/models/send.response.ts index c238663529c..4d680b5c0a1 100644 --- a/apps/cli/src/tools/send/models/send.response.ts +++ b/apps/cli/src/tools/send/models/send.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 60e78137e7e..052faa33867 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; @@ -106,6 +108,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.platformUtilsService, this.serviceContainer.environmentService, this.serviceContainer.sendApiService, + this.serviceContainer.apiService, ); const response = await cmd.run(url, options); this.processResponse(response); @@ -144,7 +147,6 @@ export class SendProgram extends BaseProgram { this.serviceContainer.auditService, this.serviceContainer.keyService, this.serviceContainer.encryptService, - this.serviceContainer.stateService, this.serviceContainer.searchService, this.serviceContainer.apiService, this.serviceContainer.organizationService, @@ -189,6 +191,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.environmentService, this.serviceContainer.searchService, this.serviceContainer.encryptService, + this.serviceContainer.apiService, ); const response = await cmd.run(id, options); this.processResponse(response); @@ -248,12 +251,14 @@ export class SendProgram extends BaseProgram { this.serviceContainer.environmentService, this.serviceContainer.searchService, this.serviceContainer.encryptService, + this.serviceContainer.apiService, ); const cmd = new SendEditCommand( this.serviceContainer.sendService, getCmd, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); const response = await cmd.run(encodedJson, options); this.processResponse(response); @@ -327,6 +332,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.environmentService, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); return await cmd.run(encodedJson, options); } diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index 6709036d11a..fadede9c71b 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 4d3215944e7..3eb0e68de09 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { program, Command } from "commander"; import { ConfirmCommand } from "./admin-console/commands/confirm.command"; @@ -111,6 +113,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.organizationUserApiService, this.serviceContainer.apiService, this.serviceContainer.eventCollectionService, + this.serviceContainer.accountService, ); const response = await command.run(object, cmd); @@ -179,7 +182,6 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.auditService, this.serviceContainer.keyService, this.serviceContainer.encryptService, - this.serviceContainer.stateService, this.serviceContainer.searchService, this.serviceContainer.apiService, this.serviceContainer.organizationService, @@ -320,6 +322,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.cipherAuthorizationService, + this.serviceContainer.accountService, ); const response = await command.run(object, id, cmd); this.processResponse(response); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 35bd68b74eb..a28d070d19e 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; @@ -28,6 +30,8 @@ import { CipherResponse } from "./models/cipher.response"; import { FolderResponse } from "./models/folder.response"; export class CreateCommand { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private folderService: FolderService, @@ -62,6 +66,8 @@ export class CreateCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } @@ -84,9 +90,7 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); @@ -134,10 +138,13 @@ export class CreateCommand { return Response.notFound(); } - if ( - cipher.organizationId == null && - !(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$)) - ) { + const activeUserId = await firstValueFrom(this.activeUserId$); + + const canAccessPremium = await firstValueFrom( + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), + ); + + if (cipher.organizationId == null && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); } @@ -150,9 +157,6 @@ export class CreateCommand { } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, @@ -169,12 +173,12 @@ export class CreateCommand { } private async createFolder(req: FolderExport) { - const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { - await this.folderApiService.save(folder); - const newFolder = await this.folderService.get(folder.id); + await this.folderApiService.save(folder, activeUserId); + const newFolder = await this.folderService.get(folder.id, activeUserId); const decFolder = await newFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 7f79c89b17c..a285f8f5b34 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -1,6 +1,7 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; 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 { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -19,6 +20,7 @@ export class DeleteCommand { private folderApiService: FolderApiServiceAbstraction, private accountProfileService: BillingAccountProfileStateService, private cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) {} async run(object: string, id: string, cmdOptions: Record): Promise { @@ -87,8 +89,9 @@ export class DeleteCommand { return Response.error("Attachment `" + id + "` was not found."); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (cipher.organizationId == null && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); @@ -103,13 +106,16 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const folder = await this.folderService.getFromState(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); } try { - await this.folderApiService.delete(id); + await this.folderApiService.delete(id, activeUserId); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/vault/models/cipher.response.ts b/apps/cli/src/vault/models/cipher.response.ts index 8df1b42f1e8..4c0f7bdff07 100644 --- a/apps/cli/src/vault/models/cipher.response.ts +++ b/apps/cli/src/vault/models/cipher.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/apps/cli/src/vault/sync.command.ts b/apps/cli/src/vault/sync.command.ts index c3c6f637538..ebf790c1c14 100644 --- a/apps/cli/src/vault/sync.command.ts +++ b/apps/cli/src/vault/sync.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SyncService } from "@bitwarden/common/platform/sync"; import { Response } from "../models/response"; @@ -19,7 +21,9 @@ export class SyncCommand { const res = new MessageResponse("Syncing complete.", null); return Response.success(res); } catch (e) { - return Response.error("Syncing failed: " + e.toString()); + const response = Response.error(e); + response.message = "Syncing failed: " + response.message; + return response; } } diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 4cb450f9c69..0668ecacdb4 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -26,8 +26,14 @@ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/node/*": ["../../libs/node/src/*"] - } + }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ] }, "include": ["src", "src/**/*.spec.ts"] } diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore index 040b2179fab..444c9a85100 100644 --- a/apps/desktop/.gitignore +++ b/apps/desktop/.gitignore @@ -2,3 +2,4 @@ dist-safari/ *.nupkg *.env PlugIns/safari.appex/ +xcuserdata/ diff --git a/apps/desktop/config/config.js b/apps/desktop/config/config.js index 404295dd0db..30a5c16bb2a 100644 --- a/apps/desktop/config/config.js +++ b/apps/desktop/config/config.js @@ -32,6 +32,8 @@ function log(configObj) { function loadConfig(configName) { try { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports return require(`./${configName}.json`); } catch (e) { if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index a1ff96ec65d..fceef8e6e7a 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -36,6 +36,7 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", + "zeroize", ] [[package]] @@ -61,11 +62,60 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arboard" @@ -83,11 +133,65 @@ dependencies = [ "x11rb", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", + "zeroize", +] + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "async-broadcast" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener", "event-listener-strategy", @@ -133,9 +237,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", @@ -161,6 +265,17 @@ 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" @@ -209,28 +324,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-task" version = "4.7.1" @@ -239,9 +332,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "1b1244b10dcd56c92219da4e14caa97e312079e185f04ba3eea25061561dc0a0" dependencies = [ "proc-macro2", "quote", @@ -287,6 +380,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bcrypt-pbkdf" version = "0.10.0" @@ -299,10 +401,13 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] [[package]] name = "bitflags" @@ -313,7 +418,7 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitwarden-russh" version = "0.1.0" -source = "git+https://github.com/bitwarden/bitwarden-russh.git?branch=km/pm-10098/clean-russh-implementation#86ff1bf2f4620a3ae5684adee31abdbee33c6f07" +source = "git+https://github.com/bitwarden/bitwarden-russh.git?rev=23b50e3bbe6d56ef19ab0e98e8bb1462cb6d77ae#23b50e3bbe6d56ef19ab0e98e8bb1462cb6d77ae" dependencies = [ "anyhow", "byteorder", @@ -326,6 +431,15 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -384,9 +498,41 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] [[package]] name = "cbc" @@ -399,23 +545,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.34" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -453,8 +589,49 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -474,6 +651,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -516,18 +699,37 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" 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.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -536,14 +738,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn", @@ -586,48 +789,76 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.129" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdc8cca144dce1c4981b5c9ab748761619979e515c3d53b5df385c677d1d007" +checksum = "ad7c7515609502d316ab9a24f67dc045132d93bfd3f00713389e90d9898bf30d" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.129" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5764c3142ab44fcf857101d12c0ddf09c34499900557c764f5ad0597159d1fc" +checksum = "8bfd16fca6fd420aebbd80d643c201ee4692114a0de208b790b9cd02ceae65fb" dependencies = [ "cc", "codespan-reporting", - "once_cell", "proc-macro2", "quote", "scratch", "syn", ] +[[package]] +name = "cxxbridge-cmd" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c33fd49f5d956a1b7ee5f7a9768d58580c6752838d92e39d0d56439efdedc35" +dependencies = [ + "clap", + "codespan-reporting", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cxxbridge-flags" -version = "1.0.129" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d422aff542b4fa28c2ce8e5cc202d42dbf24702345c1fba3087b2d3f8a1b90ff" +checksum = "be0f1077278fac36299cce8446effd19fe93a95eedb10d39265f3bf67b3036c9" [[package]] name = "cxxbridge-macro" -version = "1.0.129" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1719100f31492cd6adeeab9a0f46cdbc846e615fdb66d7b398aa46ec7fdd06f" +checksum = "3da7e4d6e74af6b79031d264b2f13c3ea70af1978083741c41ffce9308f1f24f" dependencies = [ "proc-macro2", "quote", + "rustversion", "syn", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "der" version = "0.7.9" @@ -666,27 +897,25 @@ dependencies = [ "aes", "anyhow", "arboard", - "async-stream", + "argon2", "base64", "bitwarden-russh", "byteorder", "cbc", "core-foundation", + "desktop_objc", "dirs", "ed25519", "futures", - "gio", "homedir", "interprocess", "keytar", "libc", - "libsecret", "log", + "oo7", "pin-project", "pkcs8", "rand", - "rand_chacha", - "retry", "rsa", "russh-cryptovec", "scopeguard", @@ -695,13 +924,14 @@ dependencies = [ "sha2", "ssh-encoding", "ssh-key", + "sysinfo", "thiserror", "tokio", "tokio-stream", "tokio-util", "typenum", "widestring", - "windows", + "windows 0.58.0", "zbus", "zbus_polkit", ] @@ -717,12 +947,26 @@ dependencies = [ "napi", "napi-build", "napi-derive", + "serde", + "serde_json", "tokio", "tokio-stream", "tokio-util", "windows-registry", ] +[[package]] +name = "desktop_objc" +version = "0.0.0" +dependencies = [ + "anyhow", + "cc", + "core-foundation", + "glob", + "thiserror", + "tokio", +] + [[package]] name = "desktop_proxy" version = "0.0.0" @@ -813,6 +1057,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "embed_plist" version = "1.2.2" @@ -854,12 +1104,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -881,9 +1131,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener", "pin-project-lite", @@ -891,9 +1141,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fiat-crypto" @@ -913,6 +1163,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "futures" version = "0.3.31" @@ -963,9 +1228,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ "fastrand", "futures-core", @@ -1063,97 +1328,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "gio" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be548be810e45dd31d3bbb89c6210980bb7af9bca3ea1292b5f16b75f8e394a7" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.52.0", -] - -[[package]] -name = "glib" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44" -dependencies = [ - "bitflags 2.6.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.19.9" +name = "glob" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] -name = "glib-sys" -version = "0.19.8" +name = "goblin" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" dependencies = [ - "libc", - "system-deps", + "log", + "plain", + "scroll", ] [[package]] -name = "gobject-sys" -version = "0.19.8" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -1161,12 +1362,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1179,6 +1374,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1190,24 +1394,24 @@ dependencies = [ [[package]] name = "homedir" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bed305c13ce3829a09d627f5d43ff738482a09361ae4eb8039993b55fb10e5e" +checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" dependencies = [ "cfg-if", - "nix 0.26.4", + "nix 0.29.0", "widestring", - "windows", + "windows 0.57.0", ] [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1235,11 +1439,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "keytar" @@ -1273,15 +1483,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -1289,9 +1499,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -1299,36 +1509,10 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags", "libc", ] -[[package]] -name = "libsecret" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c6ccddc706a38eca477b4d7857acd6c76c7d6fba5d47b4b2e7d800e5a17194" -dependencies = [ - "gio", - "glib", - "libc", - "libsecret-sys", -] - -[[package]] -name = "libsecret-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1af48e61f1c8e77e9705296f346e45b637754a92348a79b4c62df84d0654c2" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - [[package]] name = "link-cplusplus" version = "1.0.9" @@ -1360,6 +1544,31 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "macos_provider" +version = "0.0.0" +dependencies = [ + "desktop_core", + "futures", + "log", + "oslog", + "serde", + "serde_json", + "tokio", + "tokio-util", + "uniffi", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1368,20 +1577,27 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] -name = "memoffset" -version = "0.9.1" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ - "autocfg", + "mime", + "unicase", ] [[package]] @@ -1392,20 +1608,19 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -1417,7 +1632,7 @@ version = "2.16.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "214f07a80874bb96a8433b3cdfc84980d56c7b02e1a0d7ba4ba0db5cef785e2b" dependencies = [ - "bitflags 2.6.0", + "bitflags", "ctor", "napi-derive", "napi-sys", @@ -1427,15 +1642,15 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" +checksum = "db836caddef23662b94e16bf1f26c40eceb09d6aee5d5b06a7ac199320b69b19" [[package]] name = "napi-derive" -version = "2.16.12" +version = "2.16.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" dependencies = [ "cfg-if", "convert_case", @@ -1447,9 +1662,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" dependencies = [ "convert_case", "once_cell", @@ -1469,26 +1684,13 @@ dependencies = [ "libloading", ] -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", - "pin-utils", -] - [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cfg-if", "cfg_aliases 0.1.1", "libc", @@ -1500,11 +1702,11 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cfg-if", "cfg_aliases 0.2.1", "libc", - "memoffset 0.9.1", + "memoffset", ] [[package]] @@ -1517,6 +1719,39 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1530,10 +1765,20 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1560,6 +1805,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1601,7 +1857,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", + "bitflags", "block2", "libc", "objc2", @@ -1617,7 +1873,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", + "bitflags", "block2", "objc2", "objc2-foundation", @@ -1647,7 +1903,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", + "bitflags", "block2", "libc", "objc2", @@ -1659,7 +1915,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", + "bitflags", "block2", "objc2", "objc2-foundation", @@ -1671,7 +1927,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", + "bitflags", "block2", "objc2", "objc2-foundation", @@ -1680,9 +1936,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -1693,6 +1949,39 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oo7" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" +dependencies = [ + "aes", + "async-fs", + "async-io", + "async-lock", + "async-net", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite", + "futures-util", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "rand", + "serde", + "sha2", + "subtle", + "zbus", + "zeroize", + "zvariant", +] + [[package]] name = "opaque-debug" version = "0.3.1" @@ -1725,6 +2014,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + [[package]] name = "parking" version = "2.2.1" @@ -1754,6 +2054,23 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -1785,18 +2102,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", @@ -1870,15 +2187,21 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", @@ -1934,9 +2257,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1952,9 +2275,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1989,6 +2312,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +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", +] + [[package]] name = "recvmsg" version = "1.0.0" @@ -1997,11 +2340,11 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -2029,9 +2372,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2044,15 +2387,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "retry" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" -dependencies = [ - "rand", -] - [[package]] name = "rsa" version = "0.9.6" @@ -2092,26 +2426,38 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "salsa20" version = "0.10.2" @@ -2139,6 +2485,26 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "scrypt" version = "0.11.0" @@ -2152,11 +2518,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d0283c0a4a22a0f1b0e4edca251aa20b92fc96eaa09b84bec052f9415e9d71" +checksum = "81d3f8c9bfcc3cbb6b0179eb57042d75b1582bdc65c3cb95f3fa999509c03cbc" dependencies = [ - "bitflags 2.6.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -2165,9 +2531,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", @@ -2175,30 +2541,45 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +dependencies = [ + "serde", +] [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -2210,15 +2591,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -2277,6 +2649,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -2292,11 +2670,17 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2348,9 +2732,9 @@ dependencies = [ [[package]] name = "ssh-key" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" dependencies = [ "bcrypt-pbkdf", "ed25519-dalek", @@ -2371,6 +2755,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2379,9 +2769,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -2389,32 +2779,28 @@ dependencies = [ ] [[package]] -name = "system-deps" -version = "6.2.2" +name = "sysinfo" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tempfile" -version = "3.13.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2429,20 +2815,29 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", +] + [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -2451,9 +2846,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -2474,9 +2869,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -2484,9 +2879,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -2535,14 +2930,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", ] [[package]] @@ -2550,9 +2942,6 @@ name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -2561,17 +2950,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2580,9 +2967,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -2591,9 +2978,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -2623,16 +3010,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ - "memoffset 0.9.1", + "memoffset", "tempfile", "winapi", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" @@ -2646,6 +3039,136 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "uniffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "clap", + "uniffi_bindgen", + "uniffi_build", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "once_cell", + "paste", + "serde", + "textwrap", + "toml", + "uniffi_meta", + "uniffi_udl", +] + +[[package]] +name = "uniffi_build" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7cf32576e08104b7dc2a6a5d815f37616e66c6866c2a639fe16e6d2286b75b" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "uniffi_core" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" +dependencies = [ + "anyhow", + "bytes", + "log", + "once_cell", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] + [[package]] name = "universal-hash" version = "0.5.1" @@ -2657,10 +3180,10 @@ dependencies = [ ] [[package]] -name = "version-compare" -version = "0.2.0" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" @@ -2694,7 +3217,7 @@ version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ - "bitflags 2.6.0", + "bitflags", "rustix", "wayland-backend", "wayland-scanner", @@ -2706,7 +3229,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.6.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-scanner", @@ -2718,7 +3241,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.6.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2747,6 +3270,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + [[package]] name = "widestring" version = "1.1.0" @@ -2790,7 +3322,17 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-core", + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -2800,12 +3342,25 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.57.0", + "windows-interface 0.57.0", "windows-result 0.1.2", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-implement" version = "0.57.0" @@ -2817,6 +3372,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.57.0" @@ -2828,6 +3394,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-registry" version = "0.3.0" @@ -2835,7 +3412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a" dependencies = [ "windows-result 0.2.0", - "windows-strings", + "windows-strings 0.2.0", "windows-targets 0.52.6", ] @@ -2857,6 +3434,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.2.0" @@ -3016,9 +3603,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] @@ -3171,6 +3758,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zvariant" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 6525b38162d..6230a6bfe15 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["napi", "core", "proxy"] +members = ["napi", "core", "proxy", "macos_provider"] diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 7ed708fc577..cc407f09ec2 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -6,64 +6,58 @@ version = "0.0.0" publish = false [features] -default = ["sys"] -manual_test = [] - -sys = [ +default = [ "dep:widestring", "dep:windows", "dep:core-foundation", "dep:security-framework", "dep:security-framework-sys", - "dep:gio", - "dep:libsecret", "dep:zbus", - "dep:zbus_polkit", + "dep:zbus_polkit" ] +manual_test = [] [dependencies] aes = "=0.8.4" -anyhow = "=1.0.93" +anyhow = "=1.0.94" arboard = { version = "=3.4.1", default-features = false, features = [ "wayland-data-control", ] } -async-stream = "0.3.5" +argon2 = { version = "=0.5.3", features = ["zeroize"] } base64 = "=0.22.1" -byteorder = "1.5.0" +byteorder = "=1.5.0" cbc = { version = "=0.1.2", features = ["alloc"] } -homedir = "0.3.3" -libc = "=0.2.162" -pin-project = "1.1.5" +homedir = "=0.3.4" +pin-project = "=1.1.7" dirs = "=5.0.1" futures = "=0.3.31" interprocess = { version = "=2.2.1", features = ["tokio"] } log = "=0.4.22" rand = "=0.8.5" -retry = "=2.0.0" -russh-cryptovec = "0.7.3" +russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" sha2 = "=0.10.8" -ssh-encoding = "0.2.0" -ssh-key = { version = "0.6.6", default-features = false, features = [ - "encryption", - "ed25519", - "rsa", - "getrandom", +ssh-encoding = "=0.2.0" +ssh-key = { version = "=0.6.7", default-features = false, features = [ + "encryption", + "ed25519", + "rsa", + "getrandom", ] } -bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", branch = "km/pm-10098/clean-russh-implementation" } -tokio = { version = "=1.40.0", features = ["io-util", "sync", "macros", "net"] } +bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "23b50e3bbe6d56ef19ab0e98e8bb1462cb6d77ae" } +tokio = { version = "=1.41.1", features = ["io-util", "sync", "macros", "net"] } tokio-stream = { version = "=0.1.15", features = ["net"] } -tokio-util = "=0.7.12" -thiserror = "=1.0.68" +tokio-util = { version = "=0.7.12", features = ["codec"] } +thiserror = "=1.0.69" typenum = "=1.17.0" -rand_chacha = "=0.3.1" pkcs8 = { version = "=0.10.2", features = ["alloc", "encryption", "pem"] } rsa = "=0.9.6" ed25519 = { version = "=2.2.3", features = ["pkcs8"] } +sysinfo = { version = "0.32.0", features = ["windows"] } [target.'cfg(windows)'.dependencies] widestring = { version = "=1.1.0", optional = true } -windows = { version = "=0.57.0", features = [ +windows = { version = "=0.58.0", features = [ "Foundation", "Security_Credentials_UI", "Security_Cryptography", @@ -73,6 +67,7 @@ windows = { version = "=0.57.0", features = [ "Win32_System_WinRT", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", + "Win32_System_Pipes", ], optional = true } [target.'cfg(windows)'.dev-dependencies] @@ -80,11 +75,13 @@ keytar = "=0.1.6" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = { version = "=0.10.0", optional = true } -security-framework = { version = "=3.0.0", optional = true } -security-framework-sys = { version = "=2.12.0", optional = true } +security-framework = { version = "=3.1.0", optional = true } +security-framework-sys = { version = "=2.13.0", optional = true } +desktop_objc = { path = "../objc" } [target.'cfg(target_os = "linux")'.dependencies] -gio = { version = "=0.19.5", optional = true } -libsecret = { version = "=0.5.0", optional = true } +oo7 = "=0.3.3" +libc = "=0.2.169" + zbus = { version = "=4.4.0", optional = true } zbus_polkit = { version = "=4.0.0", optional = true } diff --git a/apps/desktop/desktop_native/core/src/autofill/macos.rs b/apps/desktop/desktop_native/core/src/autofill/macos.rs new file mode 100644 index 00000000000..08f333abe93 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/macos.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn run_command(value: String) -> Result { + desktop_objc::run_command(value).await +} diff --git a/apps/desktop/desktop_native/core/src/autofill/mod.rs b/apps/desktop/desktop_native/core/src/autofill/mod.rs new file mode 100644 index 00000000000..aacec852e90 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/mod.rs @@ -0,0 +1,6 @@ +#[allow(clippy::module_inception)] +#[cfg_attr(target_os = "linux", path = "unix.rs")] +#[cfg_attr(target_os = "windows", path = "windows.rs")] +#[cfg_attr(target_os = "macos", path = "macos.rs")] +mod autofill; +pub use autofill::*; diff --git a/apps/desktop/desktop_native/core/src/autofill/unix.rs b/apps/desktop/desktop_native/core/src/autofill/unix.rs new file mode 100644 index 00000000000..a6a961f46b1 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/unix.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn run_command(_value: String) -> Result { + todo!("Unix does not support autofill"); +} diff --git a/apps/desktop/desktop_native/core/src/autofill/windows.rs b/apps/desktop/desktop_native/core/src/autofill/windows.rs new file mode 100644 index 00000000000..6e0543af36c --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/windows.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn run_command(_value: String) -> Result { + todo!("Windows does not support autofill"); +} diff --git a/apps/desktop/desktop_native/core/src/biometric/macos.rs b/apps/desktop/desktop_native/core/src/biometric/macos.rs index 01ee4519ce6..ec09d566e1f 100644 --- a/apps/desktop/desktop_native/core/src/biometric/macos.rs +++ b/apps/desktop/desktop_native/core/src/biometric/macos.rs @@ -18,7 +18,7 @@ impl super::BiometricTrait for Biometric { bail!("platform not supported"); } - fn get_biometric_secret( + async fn get_biometric_secret( _service: &str, _account: &str, _key_material: Option, @@ -26,7 +26,7 @@ impl super::BiometricTrait for Biometric { bail!("platform not supported"); } - fn set_biometric_secret( + async fn set_biometric_secret( _service: &str, _account: &str, _secret: &str, diff --git a/apps/desktop/desktop_native/core/src/biometric/mod.rs b/apps/desktop/desktop_native/core/src/biometric/mod.rs index 72352cf2288..79be43b1bfc 100644 --- a/apps/desktop/desktop_native/core/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric/mod.rs @@ -1,13 +1,18 @@ use aes::cipher::generic_array::GenericArray; use anyhow::{anyhow, Result}; +#[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "unix.rs")] -#[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")] +#[cfg_attr(target_os = "windows", path = "windows.rs")] mod biometric; -use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; pub use biometric::Biometric; + +#[cfg(target_os = "windows")] +pub mod windows_focus; + +use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; use sha2::{Digest, Sha256}; use crate::crypto::{self, CipherString}; @@ -22,26 +27,26 @@ pub struct OsDerivedKey { pub iv_b64: String, } +#[allow(async_fn_in_trait)] pub trait BiometricTrait { - #[allow(async_fn_in_trait)] async fn prompt(hwnd: Vec, message: String) -> Result; - #[allow(async_fn_in_trait)] async fn available() -> Result; fn derive_key_material(secret: Option<&str>) -> Result; - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, key_material: Option, iv_b64: &str, ) -> Result; - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, ) -> Result; } +#[allow(unused)] fn encrypt(secret: &str, key_material: &KeyMaterial, iv_b64: &str) -> Result { let iv = base64_engine .decode(iv_b64)? @@ -53,9 +58,10 @@ fn encrypt(secret: &str, key_material: &KeyMaterial, iv_b64: &str) -> Result Result { if let CipherString::AesCbc256_B64 { iv, data } = secret { - let decrypted = crypto::decrypt_aes256(&iv, &data, key_material.derive_key()?)?; + let decrypted = crypto::decrypt_aes256(iv, data, key_material.derive_key()?)?; Ok(String::from_utf8(decrypted)?) } else { diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index 563bd1dfe52..e57b77515e3 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -33,12 +33,10 @@ impl super::BiometricTrait for Biometric { .await; match result { - Ok(result) => { - return Ok(result.is_authorized); - } + Ok(result) => Ok(result.is_authorized), Err(e) => { println!("polkit biometric error: {:?}", e); - return Ok(false); + Ok(false) } } } @@ -52,7 +50,7 @@ impl super::BiometricTrait for Biometric { return Ok(true); } } - return Ok(false); + Ok(false) } fn derive_key_material(challenge_str: Option<&str>) -> Result { @@ -68,12 +66,12 @@ impl super::BiometricTrait for Biometric { // so we use a a key derived from the iv. this key is not intended to add any security // but only a place-holder let key = Sha256::digest(challenge); - let key_b64 = base64_engine.encode(&key); - let iv_b64 = base64_engine.encode(&challenge); + let key_b64 = base64_engine.encode(key); + let iv_b64 = base64_engine.encode(challenge); Ok(OsDerivedKey { key_b64, iv_b64 }) } - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, @@ -85,11 +83,11 @@ impl super::BiometricTrait for Biometric { ))?; let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret)?; + crate::password::set_password(service, account, &encrypted_secret).await?; Ok(encrypted_secret) } - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, @@ -98,9 +96,9 @@ impl super::BiometricTrait for Biometric { "Key material is required for polkit protected keys" ))?; - let encrypted_secret = crate::password::get_password(service, account)?; + let encrypted_secret = crate::password::get_password(service, account).await?; let secret = CipherString::from_str(&encrypted_secret)?; - return Ok(decrypt(&secret, &key_material)?); + decrypt(&secret, &key_material) } } diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs index d5e8b6dc915..70813082faf 100644 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric/windows.rs @@ -1,12 +1,15 @@ -use std::str::FromStr; +use std::{ + ffi::c_void, + str::FromStr, + sync::{atomic::AtomicBool, Arc}, +}; use anyhow::{anyhow, Result}; use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; use rand::RngCore; -use retry::delay::Fixed; use sha2::{Digest, Sha256}; use windows::{ - core::{factory, h, s, HSTRING}, + core::{factory, h, HSTRING}, Foundation::IAsyncOperation, Security::{ Credentials::{ @@ -14,17 +17,7 @@ use windows::{ }, Cryptography::CryptographicBuffer, }, - Win32::{ - Foundation::HWND, - System::WinRT::IUserConsentVerifierInterop, - UI::{ - Input::KeyboardAndMouse::{ - keybd_event, GetAsyncKeyState, SetFocus, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, - VK_MENU, - }, - WindowsAndMessaging::{FindWindowA, SetForegroundWindow}, - }, - }, + Win32::{Foundation::HWND, System::WinRT::IUserConsentVerifierInterop}, }; use crate::{ @@ -32,7 +25,10 @@ use crate::{ crypto::CipherString, }; -use super::{decrypt, encrypt}; +use super::{ + decrypt, encrypt, + windows_focus::{focus_security_prompt, set_focus}, +}; /// The Windows OS implementation of the biometric trait. pub struct Biometric {} @@ -40,6 +36,8 @@ pub struct Biometric {} impl super::BiometricTrait for Biometric { async fn prompt(hwnd: Vec, message: String) -> Result { let h = isize::from_le_bytes(hwnd.clone().try_into().unwrap()); + + let h = h as *mut c_void; let window = HWND(h); // The Windows Hello prompt is displayed inside the application window. For best result we @@ -86,14 +84,14 @@ impl super::BiometricTrait for Biometric { let bitwarden = h!("Bitwarden"); let result = KeyCredentialManager::RequestCreateAsync( - &bitwarden, + bitwarden, KeyCredentialCreationOption::FailIfExists, )? .get()?; let result = match result.Status()? { KeyCredentialStatus::CredentialAlreadyExists => { - KeyCredentialManager::OpenAsync(&bitwarden)?.get()? + KeyCredentialManager::OpenAsync(bitwarden)?.get()? } KeyCredentialStatus::Success => result, _ => return Err(anyhow!("Failed to create key credential")), @@ -101,8 +99,22 @@ impl super::BiometricTrait for Biometric { let challenge_buffer = CryptographicBuffer::CreateFromByteArray(&challenge)?; let async_operation = result.Credential()?.RequestSignAsync(&challenge_buffer)?; - focus_security_prompt()?; - let signature = async_operation.get()?; + focus_security_prompt(); + + let done = Arc::new(AtomicBool::new(false)); + let done_clone = done.clone(); + let _ = std::thread::spawn(move || loop { + if !done_clone.load(std::sync::atomic::Ordering::Relaxed) { + focus_security_prompt(); + std::thread::sleep(std::time::Duration::from_millis(500)); + } else { + break; + } + }); + + let signature = async_operation.get(); + done.store(true, std::sync::atomic::Ordering::Relaxed); + let signature = signature?; if signature.Status()? != KeyCredentialStatus::Success { return Err(anyhow!("Failed to sign data")); @@ -114,12 +126,12 @@ impl super::BiometricTrait for Biometric { CryptographicBuffer::CopyToByteArray(&signature_buffer, &mut signature_value)?; let key = Sha256::digest(&*signature_value); - let key_b64 = base64_engine.encode(&key); - let iv_b64 = base64_engine.encode(&challenge); + let key_b64 = base64_engine.encode(key); + let iv_b64 = base64_engine.encode(challenge); Ok(OsDerivedKey { key_b64, iv_b64 }) } - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, @@ -131,11 +143,11 @@ impl super::BiometricTrait for Biometric { ))?; let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret)?; + crate::password::set_password(service, account, &encrypted_secret).await?; Ok(encrypted_secret) } - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, @@ -144,17 +156,17 @@ impl super::BiometricTrait for Biometric { "Key material is required for Windows Hello protected keys" ))?; - let encrypted_secret = crate::password::get_password(service, account)?; + let encrypted_secret = crate::password::get_password(service, account).await?; match CipherString::from_str(&encrypted_secret) { Ok(secret) => { // If the secret is a CipherString, it is encrypted and we need to decrypt it. let secret = decrypt(&secret, &key_material)?; - return Ok(secret); + Ok(secret) } Err(_) => { // If the secret is not a CipherString, it is not encrypted and we can return it // directly. - return Ok(encrypted_secret); + Ok(encrypted_secret) } } } @@ -166,57 +178,6 @@ fn random_challenge() -> [u8; 16] { challenge } -/// Searches for a window that looks like a security prompt and set it as focused. -/// -/// Gives up after 1.5 seconds with a delay of 500ms between each try. -fn focus_security_prompt() -> Result<()> { - unsafe fn try_find_and_set_focus( - class_name: windows::core::PCSTR, - ) -> retry::OperationResult<(), ()> { - let hwnd = unsafe { FindWindowA(class_name, None) }; - if hwnd.0 != 0 { - set_focus(hwnd); - return retry::OperationResult::Ok(()); - } - retry::OperationResult::Retry(()) - } - - let class_name = s!("Credential Dialog Xaml Host"); - retry::retry_with_index(Fixed::from_millis(500), |current_try| { - if current_try > 3 { - return retry::OperationResult::Err(()); - } - - unsafe { try_find_and_set_focus(class_name) } - }) - .map_err(|_| anyhow!("Failed to find security prompt")) -} - -fn set_focus(window: HWND) { - let mut pressed = false; - - unsafe { - // Simulate holding down Alt key to bypass windows limitations - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate#return-value - // The most significant bit indicates if the key is currently being pressed. This means the - // value will be negative if the key is pressed. - if GetAsyncKeyState(VK_MENU.0 as i32) >= 0 { - pressed = true; - keybd_event(VK_MENU.0 as u8, 0, KEYEVENTF_EXTENDEDKEY, 0); - } - SetForegroundWindow(window); - SetFocus(window); - if pressed { - keybd_event( - VK_MENU.0 as u8, - 0, - KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, - 0, - ); - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -243,20 +204,21 @@ mod tests { assert_eq!(iv.len(), 16); } - #[test] + #[tokio::test] #[cfg(feature = "manual_test")] - fn test_prompt() { + async fn test_prompt() { ::prompt( vec![0, 0, 0, 0, 0, 0, 0, 0], String::from("Hello from Rust"), ) + .await .unwrap(); } - #[test] + #[tokio::test] #[cfg(feature = "manual_test")] - fn test_available() { - assert!(::available().unwrap()) + async fn test_available() { + assert!(::available().await.unwrap()) } #[test] @@ -273,7 +235,7 @@ mod tests { match secret { CipherString::AesCbc256_B64 { iv, data: _ } => { - assert_eq!(iv_b64, base64_engine.encode(&iv)); + assert_eq!(iv_b64, base64_engine.encode(iv)); } _ => panic!("Invalid cipher string"), } @@ -290,9 +252,9 @@ mod tests { assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret") } - #[test] - fn get_biometric_secret_requires_key() { - let result = ::get_biometric_secret("", "", None); + #[tokio::test] + async fn get_biometric_secret_requires_key() { + let result = ::get_biometric_secret("", "", None).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), @@ -300,29 +262,29 @@ mod tests { ); } - #[test] - fn get_biometric_secret_handles_unencrypted_secret() { - scopeguard::defer! { - crate::password::delete_password("test", "test").unwrap(); - } + #[tokio::test] + async fn get_biometric_secret_handles_unencrypted_secret() { let test = "test"; let secret = "password"; let key_material = KeyMaterial { os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), }; - crate::password::set_password(test, test, secret).unwrap(); + crate::password::set_password(test, test, secret) + .await + .unwrap(); let result = ::get_biometric_secret(test, test, Some(key_material)) + .await .unwrap(); + crate::password::delete_password("test", "test") + .await + .unwrap(); assert_eq!(result, secret); } - #[test] - fn get_biometric_secret_handles_encrypted_secret() { - scopeguard::defer! { - crate::password::delete_password("test", "test").unwrap(); - } + #[tokio::test] + async fn get_biometric_secret_handles_encrypted_secret() { let test = "test"; let secret = CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt @@ -330,17 +292,24 @@ mod tests { os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), }; - crate::password::set_password(test, test, &secret.to_string()).unwrap(); + crate::password::set_password(test, test, &secret.to_string()) + .await + .unwrap(); let result = ::get_biometric_secret(test, test, Some(key_material)) + .await .unwrap(); + crate::password::delete_password("test", "test") + .await + .unwrap(); assert_eq!(result, "secret"); } - #[test] - fn set_biometric_secret_requires_key() { - let result = ::set_biometric_secret("", "", "", None, ""); + #[tokio::test] + async fn set_biometric_secret_requires_key() { + let result = + ::set_biometric_secret("", "", "", None, "").await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), diff --git a/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs b/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs new file mode 100644 index 00000000000..d5e92a67de6 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs @@ -0,0 +1,28 @@ +use windows::{ + core::s, + Win32::{ + Foundation::HWND, + UI::{ + Input::KeyboardAndMouse::SetFocus, + WindowsAndMessaging::{FindWindowA, SetForegroundWindow}, + }, + }, +}; + +/// Searches for a window that looks like a security prompt and set it as focused. +/// Only works when the process has permission to foreground, either by being in foreground +/// Or by being given foreground permission https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow#remarks +pub fn focus_security_prompt() { + let class_name = s!("Credential Dialog Xaml Host"); + let hwnd = unsafe { FindWindowA(class_name, None) }; + if let Ok(hwnd) = hwnd { + set_focus(hwnd); + } +} + +pub(crate) fn set_focus(window: HWND) { + unsafe { + let _ = SetForegroundWindow(window); + let _ = SetFocus(window); + } +} diff --git a/apps/desktop/desktop_native/core/src/crypto/crypto.rs b/apps/desktop/desktop_native/core/src/crypto/crypto.rs index b1fd4426fa6..a254d34c434 100644 --- a/apps/desktop/desktop_native/core/src/crypto/crypto.rs +++ b/apps/desktop/desktop_native/core/src/crypto/crypto.rs @@ -5,17 +5,13 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; -use crate::error::{CryptoError, Result}; +use crate::error::{CryptoError, KdfParamError, Result}; use super::CipherString; -pub fn decrypt_aes256( - iv: &[u8; 16], - data: &Vec, - key: GenericArray, -) -> Result> { +pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray) -> Result> { let iv = GenericArray::from_slice(iv); - let mut data = data.clone(); + let mut data = data.to_vec(); let decrypted_key_slice = cbc::Decryptor::::new(&key, iv) .decrypt_padded_mut::(&mut data) .map_err(|_| CryptoError::KeyDecrypt)?; @@ -37,3 +33,53 @@ pub fn encrypt_aes256( Ok(CipherString::AesCbc256_B64 { iv, data }) } + +pub fn argon2( + secret: &[u8], + salt: &[u8], + iterations: u32, + memory: u32, + parallelism: u32, +) -> Result<[u8; 32]> { + use argon2::*; + + let params = Params::new(memory, iterations, parallelism, Some(32)).map_err(|e| { + KdfParamError::InvalidParams(format!("Argon2 parameters are invalid: {e}",)) + })?; + let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); + + let mut hash = [0u8; 32]; + argon + .hash_password_into(secret, salt, &mut hash) + .map_err(|e| KdfParamError::InvalidParams(format!("Argon2 hashing failed: {e}",)))?; + + // Argon2 is using some stack memory that is not zeroed. Eventually some function will + // overwrite the stack, but we use this trick to force the used stack to be zeroed. + #[inline(never)] + fn clear_stack() { + std::hint::black_box([0u8; 4096]); + } + clear_stack(); + Ok(hash) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_argon2() { + let test_hash: [u8; 32] = [ + 112, 200, 85, 209, 100, 4, 246, 146, 117, 180, 152, 44, 103, 198, 75, 14, 166, 77, 201, + 22, 62, 178, 87, 224, 95, 209, 253, 68, 166, 209, 47, 218, + ]; + let secret = b"supersecurepassword"; + let salt = b"mail@example.com"; + let iterations = 3; + let memory = 1024 * 64; + let parallelism = 4; + + let hash = argon2(secret, salt, iterations, memory, parallelism).unwrap(); + assert_eq!(hash, test_hash,); + } +} diff --git a/apps/desktop/desktop_native/core/src/crypto/mod.rs b/apps/desktop/desktop_native/core/src/crypto/mod.rs index 13ac4b947bd..72f189509f8 100644 --- a/apps/desktop/desktop_native/core/src/crypto/mod.rs +++ b/apps/desktop/desktop_native/core/src/crypto/mod.rs @@ -2,4 +2,5 @@ pub use cipher_string::*; pub use crypto::*; mod cipher_string; +#[allow(clippy::module_inception)] mod crypto; diff --git a/apps/desktop/desktop_native/core/src/error.rs b/apps/desktop/desktop_native/core/src/error.rs index d3104cb6f44..34624ed630e 100644 --- a/apps/desktop/desktop_native/core/src/error.rs +++ b/apps/desktop/desktop_native/core/src/error.rs @@ -9,6 +9,8 @@ pub enum Error { #[error("Cryptography Error, {0}")] Crypto(#[from] CryptoError), + #[error("KDF Parameter Error, {0}")] + KdfParam(#[from] KdfParamError), } #[derive(Debug, Error)] @@ -29,6 +31,12 @@ pub enum CryptoError { KeyDecrypt, } +#[derive(Debug, Error)] +pub enum KdfParamError { + #[error("Invalid KDF parameters: {0}")] + InvalidParams(String), +} + // Ensure that the error messages implement Send and Sync #[cfg(test)] const _: () = { diff --git a/apps/desktop/desktop_native/core/src/ipc/client.rs b/apps/desktop/desktop_native/core/src/ipc/client.rs index 7eff8a10974..6c4ca0a6053 100644 --- a/apps/desktop/desktop_native/core/src/ipc/client.rs +++ b/apps/desktop/desktop_native/core/src/ipc/client.rs @@ -1,13 +1,11 @@ use std::path::PathBuf; +use futures::{SinkExt, StreamExt}; use interprocess::local_socket::{ tokio::{prelude::*, Stream}, GenericFilePath, ToFsName, }; use log::{error, info}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -use crate::ipc::NATIVE_MESSAGING_BUFFER_SIZE; pub async fn connect( path: PathBuf, @@ -17,7 +15,9 @@ pub async fn connect( info!("Attempting to connect to {}", path.display()); let name = path.as_os_str().to_fs_name::()?; - let mut conn = Stream::connect(name).await?; + let conn = Stream::connect(name).await?; + + let mut conn = crate::ipc::internal_ipc_codec(conn); info!("Connected to {}", path.display()); @@ -26,8 +26,6 @@ pub async fn connect( // As it's only two, we hardcode the JSON values to avoid pulling in a JSON library. send.send("{\"command\":\"connected\"}".to_owned()).await?; - let mut buffer = vec![0; NATIVE_MESSAGING_BUFFER_SIZE]; - // Listen to IPC messages loop { tokio::select! { @@ -35,7 +33,7 @@ pub async fn connect( msg = recv.recv() => { match msg { Some(msg) => { - conn.write_all(msg.as_bytes()).await?; + conn.send(msg.into()).await?; } None => { info!("Client channel closed"); @@ -45,18 +43,18 @@ pub async fn connect( }, // Forward messages from the IPC server - res = conn.read(&mut buffer[..]) => { + res = conn.next() => { match res { - Err(e) => { + Some(Err(e)) => { error!("Error reading from IPC server: {e}"); break; } - Ok(0) => { + None => { info!("Connection closed"); break; } - Ok(n) => { - let message = String::from_utf8_lossy(&buffer[..n]).to_string(); + Some(Ok(bytes)) => { + let message = String::from_utf8_lossy(&bytes).to_string(); send.send(message).await?; } } diff --git a/apps/desktop/desktop_native/core/src/ipc/mod.rs b/apps/desktop/desktop_native/core/src/ipc/mod.rs index d406b6aa137..6873f0cfb80 100644 --- a/apps/desktop/desktop_native/core/src/ipc/mod.rs +++ b/apps/desktop/desktop_native/core/src/ipc/mod.rs @@ -1,3 +1,6 @@ +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_util::codec::{Framed, LengthDelimitedCodec}; + pub mod client; pub mod server; @@ -16,6 +19,16 @@ pub const NATIVE_MESSAGING_BUFFER_SIZE: usize = 1024 * 1024; /// but ideally the messages should be processed as quickly as possible. pub const MESSAGE_CHANNEL_BUFFER: usize = 32; +/// This is the codec used for communication through the UNIX socket / Windows named pipe. +/// It's an internal implementation detail, but we want to make sure that both the client +/// and the server use the same one. +fn internal_ipc_codec(inner: T) -> Framed { + LengthDelimitedCodec::builder() + .max_frame_length(NATIVE_MESSAGING_BUFFER_SIZE) + .native_endian() + .new_framed(inner) +} + /// Resolve the path to the IPC socket. pub fn path(name: &str) -> std::path::PathBuf { #[cfg(target_os = "windows")] diff --git a/apps/desktop/desktop_native/core/src/ipc/server.rs b/apps/desktop/desktop_native/core/src/ipc/server.rs index 053b4322203..a1c77e7ab16 100644 --- a/apps/desktop/desktop_native/core/src/ipc/server.rs +++ b/apps/desktop/desktop_native/core/src/ipc/server.rs @@ -1,17 +1,20 @@ -use std::{error::Error, path::Path, vec}; +use std::{ + error::Error, + path::{Path, PathBuf}, +}; -use futures::TryFutureExt; +use futures::{SinkExt, StreamExt, TryFutureExt}; use anyhow::Result; use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions}; use log::{error, info}; use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + io::{AsyncRead, AsyncWrite}, sync::{broadcast, mpsc}, }; use tokio_util::sync::CancellationToken; -use super::{MESSAGE_CHANNEL_BUFFER, NATIVE_MESSAGING_BUFFER_SIZE}; +use super::MESSAGE_CHANNEL_BUFFER; #[derive(Debug)] pub struct Message { @@ -29,6 +32,7 @@ pub enum MessageType { } pub struct Server { + pub path: PathBuf, cancel_token: CancellationToken, server_to_clients_send: broadcast::Sender, } @@ -66,6 +70,7 @@ impl Server { // Create the server and start listening for incoming connections // in a separate task to avoid blocking the current task let server = Server { + path: path.to_owned(), cancel_token: cancel_token.clone(), server_to_clients_send, }; @@ -152,7 +157,7 @@ async fn listen_incoming( } async fn handle_connection( - mut client_stream: impl AsyncRead + AsyncWrite + Unpin, + client_stream: impl AsyncRead + AsyncWrite + Unpin, client_to_server_send: mpsc::Sender, mut server_to_clients_recv: broadcast::Receiver, cancel_token: CancellationToken, @@ -166,7 +171,7 @@ async fn handle_connection( }) .await?; - let mut buf = vec![0u8; NATIVE_MESSAGING_BUFFER_SIZE]; + let mut client_stream = crate::ipc::internal_ipc_codec(client_stream); loop { tokio::select! { @@ -179,7 +184,7 @@ async fn handle_connection( msg = server_to_clients_recv.recv() => { match msg { Ok(msg) => { - client_stream.write_all(msg.as_bytes()).await?; + client_stream.send(msg.into()).await?; }, Err(e) => { info!("Error reading message: {}", e); @@ -191,9 +196,9 @@ async fn handle_connection( // Forwards messages from the IPC clients to the server // Note that we also send connect and disconnect events so that // the server can keep track of multiple clients - result = client_stream.read(&mut buf) => { + result = client_stream.next() => { match result { - Err(e) => { + Some(Err(e)) => { info!("Error reading from client {client_id}: {e}"); client_to_server_send.send(Message { @@ -203,7 +208,7 @@ async fn handle_connection( }).await?; break; }, - Ok(0) => { + None => { info!("Client {client_id} disconnected."); client_to_server_send.send(Message { @@ -213,8 +218,8 @@ async fn handle_connection( }).await?; break; }, - Ok(size) => { - let msg = std::str::from_utf8(&buf[..size])?; + Some(Ok(bytes)) => { + let msg = std::str::from_utf8(&bytes)?; client_to_server_send.send(Message { client_id, diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index f38e6ef97b4..4a6686cc1f5 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,16 +1,10 @@ -#[cfg(feature = "sys")] +pub mod autofill; pub mod biometric; -#[cfg(feature = "sys")] pub mod clipboard; pub mod crypto; pub mod error; pub mod ipc; -#[cfg(feature = "sys")] pub mod password; -#[cfg(feature = "sys")] -pub mod process_isolation; -#[cfg(feature = "sys")] pub mod powermonitor; -#[cfg(feature = "sys")] - +pub mod process_isolation; pub mod ssh_agent; diff --git a/apps/desktop/desktop_native/core/src/password/macos.rs b/apps/desktop/desktop_native/core/src/password/macos.rs index 408706423e2..1af005acb4d 100644 --- a/apps/desktop/desktop_native/core/src/password/macos.rs +++ b/apps/desktop/desktop_native/core/src/password/macos.rs @@ -3,26 +3,22 @@ use security_framework::passwords::{ delete_generic_password, get_generic_password, set_generic_password, }; -pub fn get_password(service: &str, account: &str) -> Result { - let result = String::from_utf8(get_generic_password(&service, &account)?)?; +pub async fn get_password(service: &str, account: &str) -> Result { + let result = String::from_utf8(get_generic_password(service, account)?)?; Ok(result) } -pub fn get_password_keytar(service: &str, account: &str) -> Result { - get_password(service, account) +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { + set_generic_password(service, account, password.as_bytes())?; + Ok(()) } -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { - let result = set_generic_password(&service, &account, password.as_bytes())?; - Ok(result) -} - -pub fn delete_password(service: &str, account: &str) -> Result<()> { - let result = delete_generic_password(&service, &account)?; - Ok(result) +pub async fn delete_password(service: &str, account: &str) -> Result<()> { + delete_generic_password(service, account)?; + Ok(()) } -pub fn is_available() -> Result { +pub async fn is_available() -> Result { Ok(true) } @@ -30,18 +26,23 @@ pub fn is_available() -> Result { mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random") + .await + .unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest") + .await + .unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest") + .await + .unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!( "The specified item could not be found in the keychain.", @@ -50,9 +51,9 @@ mod tests { } } - #[test] - fn test_error_no_password() { - match get_password("Unknown", "Unknown") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("Unknown", "Unknown").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!( "The specified item could not be found in the keychain.", diff --git a/apps/desktop/desktop_native/core/src/password/mod.rs b/apps/desktop/desktop_native/core/src/password/mod.rs index 3cc0c28e044..351e896c40e 100644 --- a/apps/desktop/desktop_native/core/src/password/mod.rs +++ b/apps/desktop/desktop_native/core/src/password/mod.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "unix.rs")] #[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")] diff --git a/apps/desktop/desktop_native/core/src/password/unix.rs b/apps/desktop/desktop_native/core/src/password/unix.rs index 1817a4d62ee..c02ef619624 100644 --- a/apps/desktop/desktop_native/core/src/password/unix.rs +++ b/apps/desktop/desktop_native/core/src/password/unix.rs @@ -1,106 +1,114 @@ use anyhow::{anyhow, Result}; -use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema}; +use oo7::dbus::{self}; use std::collections::HashMap; -pub fn get_password(service: &str, account: &str) -> Result { - let res = password_lookup_sync( - Some(&get_schema()), - build_attributes(service, account), - gio::Cancellable::NONE, - )?; +pub async fn get_password(service: &str, account: &str) -> Result { + match get_password_new(service, account).await { + Ok(res) => Ok(res), + Err(_) => get_password_legacy(service, account).await, + } +} +async fn get_password_new(service: &str, account: &str) -> Result { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + let results = keyring.search_items(&attributes).await?; + let res = results.first(); match res { - Some(s) => Ok(String::from(s)), - None => Err(anyhow!("No password found")), + Some(res) => { + let secret = res.secret().await?; + Ok(String::from_utf8(secret.to_vec())?) + } + None => Err(anyhow!("no result")), } } -pub fn get_password_keytar(service: &str, account: &str) -> Result { - get_password(service, account) +// forces to read via secret service; remvove after 2025.03 +async fn get_password_legacy(service: &str, account: &str) -> Result { + println!("falling back to get legacy {} {}", service, account); + let svc = dbus::Service::new().await?; + let collection = svc.default_collection().await?; + let keyring = oo7::Keyring::DBus(collection); + let attributes = HashMap::from([("service", service), ("account", account)]); + let results = keyring.search_items(&attributes).await?; + let res = results.first(); + match res { + Some(res) => { + let secret = res.secret().await?; + println!( + "deleting legacy secret service entry {} {}", + service, account + ); + keyring.delete(&attributes).await?; + let secret_string = String::from_utf8(secret.to_vec())?; + set_password(service, account, &secret_string).await?; + Ok(secret_string) + } + None => Err(anyhow!("no result")), + } } -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { - let result = password_store_sync( - Some(&get_schema()), - build_attributes(service, account), - Some(&libsecret::COLLECTION_DEFAULT), - &format!("{}/{}", service, account), - password, - gio::Cancellable::NONE, - )?; - Ok(result) +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + keyring + .create_item( + "org.freedesktop.Secret.Generic", + &attributes, + password, + true, + ) + .await?; + Ok(()) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { - let result = password_clear_sync( - Some(&get_schema()), - build_attributes(service, account), - gio::Cancellable::NONE, - )?; - Ok(result) +pub async fn delete_password(service: &str, account: &str) -> Result<()> { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + keyring.delete(&attributes).await?; + Ok(()) } -pub fn is_available() -> Result { - let result = password_clear_sync( - Some(&get_schema()), - build_attributes("bitwardenSecretsAvailabilityTest", "test"), - gio::Cancellable::NONE, - ); - match result { +pub async fn is_available() -> Result { + match oo7::Keyring::new().await { Ok(_) => Ok(true), - Err(_) => { - println!("secret-service unavailable: {:?}", result); - Ok(false) - } + _ => Ok(false), } } -fn get_schema() -> Schema { - let mut attributes = std::collections::HashMap::new(); - attributes.insert("service", libsecret::SchemaAttributeType::String); - attributes.insert("account", libsecret::SchemaAttributeType::String); - - libsecret::Schema::new( - "org.freedesktop.Secret.Generic", - libsecret::SchemaFlags::NONE, - attributes, - ) -} - -fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> { - let mut attributes = HashMap::new(); - attributes.insert("service", service); - attributes.insert("account", account); - - attributes -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random") + .await + .unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest") + .await + .unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest") + .await + .unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { - Ok(_) => panic!("Got a result"), - Err(e) => assert_eq!("No password found", e.to_string()), + match get_password("BitwardenTest", "BitwardenTest").await { + Ok(_) => { + panic!("Got a result") + } + Err(e) => assert_eq!("no result", e.to_string()), } } - #[test] - fn test_error_no_password() { - match get_password("BitwardenTest", "BitwardenTest") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("Unknown", "Unknown").await { Ok(_) => panic!("Got a result"), - Err(e) => assert_eq!("No password found", e.to_string()), + Err(e) => assert_eq!("no result", e.to_string()), } } } diff --git a/apps/desktop/desktop_native/core/src/password/windows.rs b/apps/desktop/desktop_native/core/src/password/windows.rs index d932aabae95..32300b9f81f 100644 --- a/apps/desktop/desktop_native/core/src/password/windows.rs +++ b/apps/desktop/desktop_native/core/src/password/windows.rs @@ -13,7 +13,7 @@ use windows::{ const CRED_FLAGS_NONE: u32 = 0; -pub fn get_password<'a>(service: &str, account: &str) -> Result { +pub async fn get_password<'a>(service: &str, account: &str) -> Result { let target_name = U16CString::from_str(target_name(service, account))?; let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); @@ -42,42 +42,10 @@ pub fn get_password<'a>(service: &str, account: &str) -> Result { .to_string_lossy() }; - Ok(String::from(password)) + Ok(password) } -// Remove this after sufficient releases -pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result { - let target_name = U16CString::from_str(target_name(service, account))?; - - let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); - let credential_ptr = &mut credential; - - let result = unsafe { - CredReadW( - PCWSTR(target_name.as_ptr()), - CRED_TYPE_GENERIC, - CRED_FLAGS_NONE, - credential_ptr, - ) - }; - - scopeguard::defer!({ - unsafe { CredFree(credential as *mut _) }; - }); - - result?; - - let password = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - (*credential).CredentialBlob, - (*credential).CredentialBlobSize as usize, - )) - }; - - Ok(String::from(password)) -} - -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { let mut target_name = U16CString::from_str(target_name(service, account))?; let mut user_name = U16CString::from_str(account)?; let last_written = FILETIME { @@ -108,7 +76,7 @@ pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> Ok(()) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { +pub async fn delete_password(service: &str, account: &str) -> Result<()> { let target_name = U16CString::from_str(target_name(service, account))?; unsafe { @@ -122,7 +90,7 @@ pub fn delete_password(service: &str, account: &str) -> Result<()> { Ok(()) } -pub fn is_available() -> Result { +pub async fn is_available() -> Result { Ok(true) } @@ -142,36 +110,31 @@ fn convert_error(e: windows::core::Error) -> String { mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random") + .await + .unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest") + .await + .unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest") + .await + .unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!("Password not found.", e.to_string()), } } - #[test] - fn test_get_password_keytar() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - keytar::set_password("BitwardenTest", "BitwardenTest", "HelloFromKeytar").unwrap(); - assert_eq!( - "HelloFromKeytar", - get_password_keytar("BitwardenTest", "BitwardenTest").unwrap() - ); - } - - #[test] - fn test_error_no_password() { - match get_password("BitwardenTest", "BitwardenTest") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!("Password not found.", e.to_string()), } diff --git a/apps/desktop/desktop_native/core/src/powermonitor/mod.rs b/apps/desktop/desktop_native/core/src/powermonitor/mod.rs index 2a996395508..8329b291177 100644 --- a/apps/desktop/desktop_native/core/src/powermonitor/mod.rs +++ b/apps/desktop/desktop_native/core/src/powermonitor/mod.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(target_os = "windows", path = "unimplemented.rs")] #[cfg_attr(target_os = "macos", path = "unimplemented.rs")] diff --git a/apps/desktop/desktop_native/core/src/powermonitor/unimplemented.rs b/apps/desktop/desktop_native/core/src/powermonitor/unimplemented.rs index 0078c0bf921..efe36bcb3c7 100644 --- a/apps/desktop/desktop_native/core/src/powermonitor/unimplemented.rs +++ b/apps/desktop/desktop_native/core/src/powermonitor/unimplemented.rs @@ -3,5 +3,5 @@ pub async fn on_lock(_: tokio::sync::mpsc::Sender<()>) -> Result<(), Box bool { - return false; + false } diff --git a/apps/desktop/desktop_native/core/src/process_isolation/mod.rs b/apps/desktop/desktop_native/core/src/process_isolation/mod.rs index 7c9aaf3bcf7..30f4dbf689a 100644 --- a/apps/desktop/desktop_native/core/src/process_isolation/mod.rs +++ b/apps/desktop/desktop_native/core/src/process_isolation/mod.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")] diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/generator.rs b/apps/desktop/desktop_native/core/src/ssh_agent/generator.rs deleted file mode 100644 index fe639f20e7f..00000000000 --- a/apps/desktop/desktop_native/core/src/ssh_agent/generator.rs +++ /dev/null @@ -1,45 +0,0 @@ -use rand::SeedableRng; -use rand_chacha::ChaCha8Rng; -use ssh_key::{Algorithm, HashAlg, LineEnding}; - -use super::importer::SshKey; - -pub async fn generate_keypair(key_algorithm: String) -> Result { - // sourced from cryptographically secure entropy source, with sources for all targets: https://docs.rs/getrandom - // if it cannot be securely sourced, this will panic instead of leading to a weak key - let mut rng: ChaCha8Rng = ChaCha8Rng::from_entropy(); - - let key = match key_algorithm.as_str() { - "ed25519" => ssh_key::PrivateKey::random(&mut rng, Algorithm::Ed25519), - "rsa2048" | "rsa3072" | "rsa4096" => { - let bits = match key_algorithm.as_str() { - "rsa2048" => 2048, - "rsa3072" => 3072, - "rsa4096" => 4096, - _ => return Err(anyhow::anyhow!("Unsupported RSA key size")), - }; - let rsa_keypair = ssh_key::private::RsaKeypair::random(&mut rng, bits) - .or_else(|e| Err(anyhow::anyhow!(e.to_string())))?; - - let private_key = ssh_key::PrivateKey::new( - ssh_key::private::KeypairData::from(rsa_keypair), - "".to_string(), - ) - .or_else(|e| Err(anyhow::anyhow!(e.to_string())))?; - Ok(private_key) - } - _ => { - return Err(anyhow::anyhow!("Unsupported key algorithm")); - } - } - .or_else(|e| Err(anyhow::anyhow!(e.to_string())))?; - - let private_key_openssh = key - .to_openssh(LineEnding::LF) - .or_else(|e| Err(anyhow::anyhow!(e.to_string())))?; - Ok(SshKey { - private_key: private_key_openssh.to_string(), - public_key: key.public_key().to_string(), - key_fingerprint: key.fingerprint(HashAlg::Sha256).to_string(), - }) -} diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/importer.rs b/apps/desktop/desktop_native/core/src/ssh_agent/importer.rs index 3d643e764c7..52464487ec5 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/importer.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/importer.rs @@ -27,61 +27,43 @@ pub fn import_key( password: String, ) -> Result { match encoded_key.lines().next() { - Some(PKCS1_HEADER) => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::UnsupportedKeyType, + Some(PKCS1_HEADER) => Ok(SshKeyImportResult { + status: SshKeyImportStatus::UnsupportedKeyType, + ssh_key: None, + }), + Some(PKCS8_UNENCRYPTED_HEADER) => match import_pkcs8_key(encoded_key, None) { + Ok(result) => Ok(result), + Err(_) => Ok(SshKeyImportResult { + status: SshKeyImportStatus::ParsingError, ssh_key: None, - }); - } - Some(PKCS8_UNENCRYPTED_HEADER) => { - return match import_pkcs8_key(encoded_key, None) { - Ok(result) => Ok(result), - Err(_) => Ok(SshKeyImportResult { + }), + }, + Some(PKCS8_ENCRYPTED_HEADER) => match import_pkcs8_key(encoded_key, Some(password)) { + Ok(result) => Ok(result), + Err(err) => match err { + SshKeyImportError::PasswordRequired => Ok(SshKeyImportResult { + status: SshKeyImportStatus::PasswordRequired, + ssh_key: None, + }), + SshKeyImportError::WrongPassword => Ok(SshKeyImportResult { + status: SshKeyImportStatus::WrongPassword, + ssh_key: None, + }), + SshKeyImportError::ParsingError => Ok(SshKeyImportResult { status: SshKeyImportStatus::ParsingError, ssh_key: None, }), - }; - } - Some(PKCS8_ENCRYPTED_HEADER) => match import_pkcs8_key(encoded_key, Some(password)) { - Ok(result) => { - return Ok(result); - } - Err(err) => match err { - SshKeyImportError::PasswordRequired => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::PasswordRequired, - ssh_key: None, - }); - } - SshKeyImportError::WrongPassword => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::WrongPassword, - ssh_key: None, - }); - } - SshKeyImportError::ParsingError => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::ParsingError, - ssh_key: None, - }); - } }, }, - Some(OPENSSH_HEADER) => { - return import_openssh_key(encoded_key, password); - } - Some(_) => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::ParsingError, - ssh_key: None, - }); - } - None => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::ParsingError, - ssh_key: None, - }); - } + Some(OPENSSH_HEADER) => import_openssh_key(encoded_key, password), + Some(_) => Ok(SshKeyImportResult { + status: SshKeyImportStatus::ParsingError, + ssh_key: None, + }), + None => Ok(SshKeyImportResult { + status: SshKeyImportStatus::ParsingError, + ssh_key: None, + }), } } @@ -152,14 +134,14 @@ fn import_pkcs8_key( let pk: Ed25519Keypair = Ed25519Keypair::from(Ed25519PrivateKey::from_bytes(&pk.secret_key)); let private_key = ssh_key::private::PrivateKey::from(pk); - return Ok(SshKeyImportResult { + Ok(SshKeyImportResult { status: SshKeyImportStatus::Success, ssh_key: Some(SshKey { private_key: private_key.to_openssh(LineEnding::LF).unwrap().to_string(), public_key: private_key.public_key().to_string(), key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), }), - }); + }) } KeyType::Rsa => { let pk: rsa::RsaPrivateKey = match password { @@ -179,7 +161,7 @@ fn import_pkcs8_key( match rsa_keypair { Ok(rsa_keypair) => { let private_key = ssh_key::private::PrivateKey::from(rsa_keypair); - return Ok(SshKeyImportResult { + Ok(SshKeyImportResult { status: SshKeyImportStatus::Success, ssh_key: Some(SshKey { private_key: private_key @@ -189,22 +171,18 @@ fn import_pkcs8_key( public_key: private_key.public_key().to_string(), key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), }), - }); - } - Err(_) => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::ParsingError, - ssh_key: None, - }); + }) } + Err(_) => Ok(SshKeyImportResult { + status: SshKeyImportStatus::ParsingError, + ssh_key: None, + }), } } - _ => { - return Ok(SshKeyImportResult { - status: SshKeyImportStatus::UnsupportedKeyType, - ssh_key: None, - }); - } + _ => Ok(SshKeyImportResult { + status: SshKeyImportStatus::UnsupportedKeyType, + ssh_key: None, + }), } } @@ -392,4 +370,33 @@ mod tests { let result = import_key(private_key.to_string(), "".to_string()).unwrap(); assert_eq!(result.status, SshKeyImportStatus::UnsupportedKeyType); } + + // Putty-exported keys should be supported, but are not due to a parser incompatibility. + // Should this test start failing, please change it to expect a correct key, and + // make sure the documentation support for putty-exported keys this is updated. + // https://bitwarden.atlassian.net/browse/PM-14989 + #[test] + fn import_key_ed25519_putty() { + let private_key = include_str!("./test_keys/ed25519_putty_openssh_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.status, SshKeyImportStatus::ParsingError); + } + + // Putty-exported keys should be supported, but are not due to a parser incompatibility. + // Should this test start failing, please change it to expect a correct key, and + // make sure the documentation support for putty-exported keys this is updated. + // https://bitwarden.atlassian.net/browse/PM-14989 + #[test] + fn import_key_rsa_openssh_putty() { + let private_key = include_str!("./test_keys/rsa_putty_openssh_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.status, SshKeyImportStatus::ParsingError); + } + + #[test] + fn import_key_rsa_pkcs8_putty() { + let private_key = include_str!("./test_keys/rsa_putty_pkcs1_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.status, SshKeyImportStatus::UnsupportedKeyType); + } } diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs index ad0ac837afc..4e304ccea78 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::sync::{ + atomic::{AtomicBool, AtomicU32}, + Arc, +}; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; @@ -10,33 +13,77 @@ use bitwarden_russh::ssh_agent::{self, Key}; #[cfg_attr(target_os = "linux", path = "unix.rs")] mod platform_ssh_agent; -pub mod generator; -pub mod importer; +#[cfg(any(target_os = "linux", target_os = "macos"))] +mod peercred_unix_listener_stream; +pub mod importer; +pub mod peerinfo; #[derive(Clone)] pub struct BitwardenDesktopAgent { keystore: ssh_agent::KeyStore, cancellation_token: CancellationToken, - show_ui_request_tx: tokio::sync::mpsc::Sender<(u32, String)>, + show_ui_request_tx: tokio::sync::mpsc::Sender, get_ui_response_rx: Arc>>, - request_id: Arc>, + request_id: Arc, + /// before first unlock, or after account switching, listing keys should require an unlock to get a list of public keys + needs_unlock: Arc, + is_running: Arc, } -impl BitwardenDesktopAgent { - async fn get_request_id(&self) -> u32 { - let mut request_id = self.request_id.lock().await; - *request_id += 1; - *request_id - } +pub struct SshAgentUIRequest { + pub request_id: u32, + pub cipher_id: Option, + pub process_name: String, + pub is_list: bool, } -impl ssh_agent::Agent for BitwardenDesktopAgent { - async fn confirm(&self, ssh_key: Key) -> bool { +impl ssh_agent::Agent for BitwardenDesktopAgent { + async fn confirm(&self, ssh_key: Key, info: &peerinfo::models::PeerInfo) -> bool { + if !self.is_running() { + println!("[BitwardenDesktopAgent] Agent is not running, but tried to call confirm"); + return false; + } + let request_id = self.get_request_id().await; + println!( + "[SSH Agent] Confirming request from application: {}", + info.process_name() + ); let mut rx_channel = self.get_ui_response_rx.lock().await.resubscribe(); self.show_ui_request_tx - .send((request_id, ssh_key.cipher_uuid.clone())) + .send(SshAgentUIRequest { + request_id, + cipher_id: Some(ssh_key.cipher_uuid.clone()), + process_name: info.process_name().to_string(), + is_list: false, + }) + .await + .expect("Should send request to ui"); + while let Ok((id, response)) = rx_channel.recv().await { + if id == request_id { + return response; + } + } + false + } + + async fn can_list(&self, info: &peerinfo::models::PeerInfo) -> bool { + if !self.needs_unlock.load(std::sync::atomic::Ordering::Relaxed) { + return true; + } + + let request_id = self.get_request_id().await; + + let mut rx_channel = self.get_ui_response_rx.lock().await.resubscribe(); + let message = SshAgentUIRequest { + request_id, + cipher_id: None, + process_name: info.process_name().to_string(), + is_list: true, + }; + self.show_ui_request_tx + .send(message) .await .expect("Should send request to ui"); while let Ok((id, response)) = rx_channel.recv().await { @@ -50,7 +97,13 @@ impl ssh_agent::Agent for BitwardenDesktopAgent { impl BitwardenDesktopAgent { pub fn stop(&self) { - self.cancellation_token.cancel(); + if !self.is_running() { + println!("[BitwardenDesktopAgent] Tried to stop agent while it is not running"); + return; + } + + self.is_running + .store(false, std::sync::atomic::Ordering::Relaxed); self.keystore .0 .write() @@ -62,11 +115,20 @@ impl BitwardenDesktopAgent { &mut self, new_keys: Vec<(String, String, String)>, ) -> Result<(), anyhow::Error> { + if !self.is_running() { + return Err(anyhow::anyhow!( + "[BitwardenDesktopAgent] Tried to set keys while agent is not running" + )); + } + let keystore = &mut self.keystore; keystore.0.write().expect("RwLock is not poisoned").clear(); + self.needs_unlock + .store(true, std::sync::atomic::Ordering::Relaxed); + for (key, name, cipher_id) in new_keys.iter() { - match parse_key_safe(&key) { + match parse_key_safe(key) { Ok(private_key) => { let public_key_bytes = private_key .public_key() @@ -91,6 +153,12 @@ impl BitwardenDesktopAgent { } pub fn lock(&mut self) -> Result<(), anyhow::Error> { + if !self.is_running() { + return Err(anyhow::anyhow!( + "[BitwardenDesktopAgent] Tried to lock agent, but it is not running" + )); + } + let keystore = &mut self.keystore; keystore .0 @@ -102,6 +170,29 @@ impl BitwardenDesktopAgent { }); Ok(()) } + + pub fn clear_keys(&mut self) -> Result<(), anyhow::Error> { + let keystore = &mut self.keystore; + keystore.0.write().expect("RwLock is not poisoned").clear(); + self.needs_unlock + .store(true, std::sync::atomic::Ordering::Relaxed); + + Ok(()) + } + + async fn get_request_id(&self) -> u32 { + if !self.is_running() { + println!("[BitwardenDesktopAgent] Agent is not running, but tried to get request id"); + return 0; + } + + self.request_id + .fetch_add(1, std::sync::atomic::Ordering::Relaxed) + } + + pub fn is_running(&self) -> bool { + self.is_running.load(std::sync::atomic::Ordering::Relaxed) + } } fn parse_key_safe(pem: &str) -> Result { diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs index 69399ae7530..094144effe1 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs @@ -1,32 +1,48 @@ +use futures::Stream; +use std::os::windows::prelude::AsRawHandle as _; use std::{ io, pin::Pin, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, task::{Context, Poll}, }; - -use futures::Stream; use tokio::{ net::windows::named_pipe::{NamedPipeServer, ServerOptions}, select, }; use tokio_util::sync::CancellationToken; +use windows::Win32::{Foundation::HANDLE, System::Pipes::GetNamedPipeClientProcessId}; + +use crate::ssh_agent::peerinfo::{self, models::PeerInfo}; const PIPE_NAME: &str = r"\\.\pipe\openssh-ssh-agent"; #[pin_project::pin_project] pub struct NamedPipeServerStream { - rx: tokio::sync::mpsc::Receiver, + rx: tokio::sync::mpsc::Receiver<(NamedPipeServer, PeerInfo)>, } impl NamedPipeServerStream { - pub fn new(cancellation_token: CancellationToken) -> Self { + pub fn new(cancellation_token: CancellationToken, is_running: Arc) -> Self { let (tx, rx) = tokio::sync::mpsc::channel(16); tokio::spawn(async move { println!( "[SSH Agent Native Module] Creating named pipe server on {}", PIPE_NAME ); - let mut listener = ServerOptions::new().create(PIPE_NAME).unwrap(); + let mut listener = match ServerOptions::new().create(PIPE_NAME) { + Ok(pipe) => pipe, + Err(err) => { + println!("[SSH Agent Native Module] Encountered an error creating the first pipe. The system's openssh service must likely be disabled"); + println!("[SSH Agent Natvie Module] error: {}", err); + cancellation_token.cancel(); + is_running.store(false, Ordering::Relaxed); + return; + } + }; loop { println!("[SSH Agent Native Module] Waiting for connection"); select! { @@ -36,8 +52,35 @@ impl NamedPipeServerStream { } _ = listener.connect() => { println!("[SSH Agent Native Module] Incoming connection"); - tx.send(listener).await.unwrap(); - listener = ServerOptions::new().create(PIPE_NAME).unwrap(); + let handle = HANDLE(listener.as_raw_handle()); + let mut pid = 0; + unsafe { + if let Err(e) = GetNamedPipeClientProcessId(handle, &mut pid) { + println!("Error getting named pipe client process id {}", e); + continue + } + }; + + let peer_info = peerinfo::gather::get_peer_info(pid); + let peer_info = match peer_info { + Err(err) => { + println!("Failed getting process info for pid {} {}", pid, err); + continue + }, + Ok(info) => info, + }; + + tx.send((listener, peer_info)).await.unwrap(); + + listener = match ServerOptions::new().create(PIPE_NAME) { + Ok(pipe) => pipe, + Err(err) => { + println!("[SSH Agent Native Module] Encountered an error creating a new pipe {}", err); + cancellation_token.cancel(); + is_running.store(false, Ordering::Relaxed); + return; + } + }; } } } @@ -47,12 +90,12 @@ impl NamedPipeServerStream { } impl Stream for NamedPipeServerStream { - type Item = io::Result; + type Item = io::Result<(NamedPipeServer, PeerInfo)>; fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll>> { + ) -> Poll>> { let this = self.project(); this.rx.poll_recv(cx).map(|v| v.map(Ok)) diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs new file mode 100644 index 00000000000..f0114fc08da --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs @@ -0,0 +1,72 @@ +use futures::Stream; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::net::{UnixListener, UnixStream}; + +use super::peerinfo; +use super::peerinfo::models::PeerInfo; + +#[derive(Debug)] +pub struct PeercredUnixListenerStream { + inner: UnixListener, +} + +impl PeercredUnixListenerStream { + pub fn new(listener: UnixListener) -> Self { + Self { inner: listener } + } +} + +impl Stream for PeercredUnixListenerStream { + type Item = io::Result<(UnixStream, PeerInfo)>; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + match self.inner.poll_accept(cx) { + Poll::Ready(Ok((stream, _))) => { + let pid = match stream.peer_cred() { + Ok(peer) => match peer.pid() { + Some(pid) => pid, + None => { + return Poll::Ready(Some(Err(io::Error::new( + io::ErrorKind::Other, + "Failed to get peer PID", + )))); + } + }, + Err(err) => { + return Poll::Ready(Some(Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed to get peer credentials: {}", err), + )))); + } + }; + let peer_info = peerinfo::gather::get_peer_info(pid as u32); + match peer_info { + Ok(info) => Poll::Ready(Some(Ok((stream, info)))), + Err(err) => Poll::Ready(Some(Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed to get peer info: {}", err), + )))), + } + } + Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))), + Poll::Pending => Poll::Pending, + } + } +} + +impl AsRef for PeercredUnixListenerStream { + fn as_ref(&self) -> &UnixListener { + &self.inner + } +} + +impl AsMut for PeercredUnixListenerStream { + fn as_mut(&mut self) -> &mut UnixListener { + &mut self.inner + } +} diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs new file mode 100644 index 00000000000..699203d613d --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs @@ -0,0 +1,23 @@ +use sysinfo::{Pid, System}; + +use super::models::PeerInfo; + +pub fn get_peer_info(peer_pid: u32) -> Result { + let s = System::new_all(); + if let Some(process) = s.process(Pid::from_u32(peer_pid)) { + let peer_process_name = match process.name().to_str() { + Some(name) => name.to_string(), + None => { + return Err("Failed to get process name".to_string()); + } + }; + + return Ok(PeerInfo::new( + peer_pid, + process.pid().as_u32(), + peer_process_name, + )); + } + + Err("Failed to get process".to_string()) +} diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/mod.rs new file mode 100644 index 00000000000..fb12aa66e09 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/mod.rs @@ -0,0 +1,2 @@ +pub mod gather; +pub mod models; diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs new file mode 100644 index 00000000000..823d912883e --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs @@ -0,0 +1,32 @@ +/** +* Peerinfo represents the information of a peer process connecting over a socket. +* This can be later extended to include more information (icon, app name) for the corresponding application. +*/ +#[derive(Debug)] +pub struct PeerInfo { + uid: u32, + pid: u32, + process_name: String, +} + +impl PeerInfo { + pub fn new(uid: u32, pid: u32, process_name: String) -> Self { + Self { + uid, + pid, + process_name, + } + } + + pub fn uid(&self) -> u32 { + self.uid + } + + pub fn pid(&self) -> u32 { + self.pid + } + + pub fn process_name(&self) -> &str { + &self.process_name + } +} diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/ed25519_putty_openssh_unencrypted b/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/ed25519_putty_openssh_unencrypted new file mode 100644 index 00000000000..aa9c01b8dbe --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/ed25519_putty_openssh_unencrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz +c2gtZWQyNTUxOQAAACDp0/9zFBCyZs5BFqXCJN5i1DTanzPGHpUeo2LP8FmQ9wAA +AKCyIXPqsiFz6gAAAAtzc2gtZWQyNTUxOQAAACDp0/9zFBCyZs5BFqXCJN5i1DTa +nzPGHpUeo2LP8FmQ9wAAAEDQioomhjmD+sh2nsxfQLJ5YYGASNUAlUZHe9Jx0p47 +H+nT/3MUELJmzkEWpcIk3mLUNNqfM8YelR6jYs/wWZD3AAAAEmVkZHNhLWtleS0y +MDI0MTExOAECAwQFBgcICQoL +-----END OPENSSH PRIVATE KEY----- diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/rsa_putty_openssh_unencrypted b/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/rsa_putty_openssh_unencrypted new file mode 100644 index 00000000000..bbb8edfe362 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/rsa_putty_openssh_unencrypted @@ -0,0 +1,30 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz +c2gtcnNhAAAAAwEAAQAAAQEAootgTLcKjSgPLS2+RT3ZElhktL1CwIyM/+3IqEq0 +0fl/rRHBT8otklV3Ld7DOR50HVZSoV0u9qs0WOdxcjEJlJACDClmxZmFr0BQ/E2y +V5xzuMZj3Mj+fL26jTmM3ueRHZ0tU5ubSFvINIFyDGG70F7VgkpBA8zsviineMSU +t1iIPi/6feL6h7QAFUk6JdQJpPTs9Nb2+DAQ9lMS2614cxLXkfUIXA4NvHMfZGdU +dq1mBJIAWZ4PPJ6naUcu0lVYjuVEAOE4UoHxr6YlW4yyAF/I1YXBFpcHG7P0egvg +neTPli5Wzum0XDsOPivqr6z2E5k7nzyGXUaP5MjRfDDVLwAAA9D6lTpR+pU6UQAA +AAdzc2gtcnNhAAABAQCii2BMtwqNKA8tLb5FPdkSWGS0vULAjIz/7cioSrTR+X+t +EcFPyi2SVXct3sM5HnQdVlKhXS72qzRY53FyMQmUkAIMKWbFmYWvQFD8TbJXnHO4 +xmPcyP58vbqNOYze55EdnS1Tm5tIW8g0gXIMYbvQXtWCSkEDzOy+KKd4xJS3WIg+ +L/p94vqHtAAVSTol1Amk9Oz01vb4MBD2UxLbrXhzEteR9QhcDg28cx9kZ1R2rWYE +kgBZng88nqdpRy7SVViO5UQA4ThSgfGvpiVbjLIAX8jVhcEWlwcbs/R6C+Cd5M+W +LlbO6bRcOw4+K+qvrPYTmTufPIZdRo/kyNF8MNUvAAAAAwEAAQAAAQB6YVPVDq9s +DfA3RMyQF3vbOyA/kIu0q13xx1cflnfD7AT8CnUwnPloxt5fc+wqkko8WGUIRz93 +yvkzwrYAkvkymKZh/734IpmrlFIlVF5lZk8enIhNkCtDQho2AFGW9mSlFlUtMOhe +N3RqS9fRiLg+r1gzq7J9qQnKNpO48tFBpLkIqr8nZOVhEn8IASrQYBUoocClNrv6 +Pdl8ni5jqnZ/0K0nq4+41Ag1VMI4LUcRCucid8ci1HKdOmGXkvClbzuFMWv3UC0k +qDgzg/gOIgj75I7B34FYVx47UGZ6jmC7iRkHd6RiCHYkmsDSjRQHR6eRbtLPdl9w +TlG2NrwkbSlhAAAAgQCapfJLqew9aK8PKfe3FwiC9sb0itCAXPXHhD+pQ6Tl9UMZ +hmnG2g9qbowCprz3/kyix+nWL/Kx7eKAZYH2MBz6cxfqs2A+BSuxvX/hsnvF96BP +u1I47rGrd0NC78DTY2NDO4Ccirx6uN+AoCl4cC+KC00Kykww6TTEBrQsdQTk5QAA +AIEA7JwbIIMwDiQUt3EY/VU0SYvg67aOiyOYEWplSWCGdT58jnfS1H95kGVw+qXR +eSQ0VNv6LBz7XDRpfQlNXDNJRnDZuHBbk+T9ZwnynRLWuzK7VqZBPJoNoyLFSMW2 +DBhLVKIrg0MsBAnRBMDUlVDlzs2LoNLEra3dj8Zb9vMdlbEAAACBAK/db27GfXXg +OikZkIqWiFgBArtj0T4iFc7BLUJUeFtl0RP9LLjfvaxSdA1cmVYzzkgmuc2iZLF0 +37zuPkDrfYVRiw8rSihT3D+WDt3/Tt013WCuxVQOQSW+Qtw6yZpM92DKncbvYsUy +5DNklW1+TYxyn2ltM7SaZjmF8UeoTnDfAAAAEHJzYS1rZXktMjAyNDExMTgBAgME +BQYHCAkK +-----END OPENSSH PRIVATE KEY----- diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/rsa_putty_pkcs1_unencrypted b/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/rsa_putty_pkcs1_unencrypted new file mode 100644 index 00000000000..e5bedfbdd24 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/ssh_agent/test_keys/rsa_putty_pkcs1_unencrypted @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAootgTLcKjSgPLS2+RT3ZElhktL1CwIyM/+3IqEq00fl/rRHB +T8otklV3Ld7DOR50HVZSoV0u9qs0WOdxcjEJlJACDClmxZmFr0BQ/E2yV5xzuMZj +3Mj+fL26jTmM3ueRHZ0tU5ubSFvINIFyDGG70F7VgkpBA8zsviineMSUt1iIPi/6 +feL6h7QAFUk6JdQJpPTs9Nb2+DAQ9lMS2614cxLXkfUIXA4NvHMfZGdUdq1mBJIA +WZ4PPJ6naUcu0lVYjuVEAOE4UoHxr6YlW4yyAF/I1YXBFpcHG7P0egvgneTPli5W +zum0XDsOPivqr6z2E5k7nzyGXUaP5MjRfDDVLwIDAQABAoIBAHphU9UOr2wN8DdE +zJAXe9s7ID+Qi7SrXfHHVx+Wd8PsBPwKdTCc+WjG3l9z7CqSSjxYZQhHP3fK+TPC +tgCS+TKYpmH/vfgimauUUiVUXmVmTx6ciE2QK0NCGjYAUZb2ZKUWVS0w6F43dGpL +19GIuD6vWDOrsn2pCco2k7jy0UGkuQiqvydk5WESfwgBKtBgFSihwKU2u/o92Xye +LmOqdn/QrSerj7jUCDVUwjgtRxEK5yJ3xyLUcp06YZeS8KVvO4Uxa/dQLSSoODOD ++A4iCPvkjsHfgVhXHjtQZnqOYLuJGQd3pGIIdiSawNKNFAdHp5Fu0s92X3BOUbY2 +vCRtKWECgYEA7JwbIIMwDiQUt3EY/VU0SYvg67aOiyOYEWplSWCGdT58jnfS1H95 +kGVw+qXReSQ0VNv6LBz7XDRpfQlNXDNJRnDZuHBbk+T9ZwnynRLWuzK7VqZBPJoN +oyLFSMW2DBhLVKIrg0MsBAnRBMDUlVDlzs2LoNLEra3dj8Zb9vMdlbECgYEAr91v +bsZ9deA6KRmQipaIWAECu2PRPiIVzsEtQlR4W2XRE/0suN+9rFJ0DVyZVjPOSCa5 +zaJksXTfvO4+QOt9hVGLDytKKFPcP5YO3f9O3TXdYK7FVA5BJb5C3DrJmkz3YMqd +xu9ixTLkM2SVbX5NjHKfaW0ztJpmOYXxR6hOcN8CgYASLZAb+Fg5zeXVjhfYZrJk +sB1wno7m+64UMHNlpsfNvCY/n88Pyldhk5mReCnWv8RRfLEEsJlTJSexloReAAay +JbtkYyV2AFLDls0P6kGbEjO4XX+Hk2JW1TYI+D+bQEaRUwA6zm9URBjN3661Zgix +0bLXgTnhCgmKoTexik4MkQKBgEZR14XGzlG81+SpOTeBG4F83ffJ4NfkTy395jf4 +iKubGa/Rcvl1VWU7DvZsyU9Dpb8J5Q+JWJPwdKoZ5UCWKPmO8nidSai4Z3/xY352 +4LTpHdzT5UlH7drGqftfck9FaUEFo3LxM2BAiijWlj1S3HVFO+Ku7JbRigCEQ0bw +0HSnAoGBAJql8kup7D1orw8p97cXCIL2xvSK0IBc9ceEP6lDpOX1QxmGacbaD2pu +jAKmvPf+TKLH6dYv8rHt4oBlgfYwHPpzF+qzYD4FK7G9f+Gye8X3oE+7Ujjusat3 +Q0LvwNNjY0M7gJyKvHq434CgKXhwL4oLTQrKTDDpNMQGtCx1BOTl +-----END RSA PRIVATE KEY----- diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs index c1a39506660..ae03421a425 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs @@ -1,6 +1,11 @@ use std::{ collections::HashMap, - sync::{Arc, RwLock}, + fs, + os::unix::fs::PermissionsExt, + sync::{ + atomic::{AtomicBool, AtomicU32}, + Arc, RwLock, + }, }; use bitwarden_russh::ssh_agent; @@ -8,21 +13,23 @@ use homedir::my_home; use tokio::{net::UnixListener, sync::Mutex}; use tokio_util::sync::CancellationToken; -use super::BitwardenDesktopAgent; +use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; + +use super::{BitwardenDesktopAgent, SshAgentUIRequest}; impl BitwardenDesktopAgent { pub async fn start_server( - auth_request_tx: tokio::sync::mpsc::Sender<(u32, String)>, + auth_request_tx: tokio::sync::mpsc::Sender, auth_response_rx: Arc>>, ) -> Result { - use std::path::PathBuf; - let agent = BitwardenDesktopAgent { keystore: ssh_agent::KeyStore(Arc::new(RwLock::new(HashMap::new()))), cancellation_token: CancellationToken::new(), show_ui_request_tx: auth_request_tx, get_ui_response_rx: auth_response_rx, - request_id: Arc::new(tokio::sync::Mutex::new(0)), + request_id: Arc::new(AtomicU32::new(0)), + needs_unlock: Arc::new(AtomicBool::new(false)), + is_running: Arc::new(AtomicBool::new(false)), }; let cloned_agent_state = agent.clone(); tokio::spawn(async move { @@ -33,7 +40,12 @@ impl BitwardenDesktopAgent { let ssh_agent_directory = match my_home() { Ok(Some(home)) => home, - _ => PathBuf::from("/tmp/"), + _ => { + println!( + "[SSH Agent Native Module] Could not determine home directory" + ); + return; + } }; ssh_agent_directory .join(".bitwarden-ssh-agent.sock") @@ -48,19 +60,45 @@ impl BitwardenDesktopAgent { ssh_path ); let sockname = std::path::Path::new(&ssh_path); - let _ = std::fs::remove_file(sockname); + if let Err(e) = std::fs::remove_file(sockname) { + println!( + "[SSH Agent Native Module] Could not remove existing socket file: {}", + e + ); + if e.kind() != std::io::ErrorKind::NotFound { + return; + } + } + match UnixListener::bind(sockname) { Ok(listener) => { - let wrapper = tokio_stream::wrappers::UnixListenerStream::new(listener); + // Only the current user should be able to access the socket + if let Err(e) = fs::set_permissions(sockname, fs::Permissions::from_mode(0o600)) + { + println!( + "[SSH Agent Native Module] Could not set socket permissions: {}", + e + ); + return; + } + + let stream = PeercredUnixListenerStream::new(listener); + let cloned_keystore = cloned_agent_state.keystore.clone(); let cloned_cancellation_token = cloned_agent_state.cancellation_token.clone(); + cloned_agent_state + .is_running + .store(true, std::sync::atomic::Ordering::Relaxed); let _ = ssh_agent::serve( - wrapper, - cloned_agent_state, + stream, + cloned_agent_state.clone(), cloned_keystore, cloned_cancellation_token, ) .await; + cloned_agent_state + .is_running + .store(false, std::sync::atomic::Ordering::Relaxed); println!("[SSH Agent Native Module] SSH Agent server exited"); } Err(e) => { diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs index fd6d9dacb9f..bc63ef552b7 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs @@ -3,16 +3,19 @@ pub mod named_pipe_listener_stream; use std::{ collections::HashMap, - sync::{Arc, RwLock}, + sync::{ + atomic::{AtomicBool, AtomicU32}, + Arc, RwLock, + }, }; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; -use super::BitwardenDesktopAgent; +use super::{BitwardenDesktopAgent, SshAgentUIRequest}; impl BitwardenDesktopAgent { pub async fn start_server( - auth_request_tx: tokio::sync::mpsc::Sender<(u32, String)>, + auth_request_tx: tokio::sync::mpsc::Sender, auth_response_rx: Arc>>, ) -> Result { let agent_state = BitwardenDesktopAgent { @@ -20,14 +23,20 @@ impl BitwardenDesktopAgent { show_ui_request_tx: auth_request_tx, get_ui_response_rx: auth_response_rx, cancellation_token: CancellationToken::new(), - request_id: Arc::new(tokio::sync::Mutex::new(0)), + request_id: Arc::new(AtomicU32::new(0)), + needs_unlock: Arc::new(AtomicBool::new(true)), + is_running: Arc::new(AtomicBool::new(true)), }; let stream = named_pipe_listener_stream::NamedPipeServerStream::new( agent_state.cancellation_token.clone(), + agent_state.is_running.clone(), ); let cloned_agent_state = agent_state.clone(); tokio::spawn(async move { + cloned_agent_state + .is_running + .store(true, std::sync::atomic::Ordering::Relaxed); let _ = ssh_agent::serve( stream, cloned_agent_state.clone(), @@ -35,6 +44,9 @@ impl BitwardenDesktopAgent { cloned_agent_state.cancellation_token.clone(), ) .await; + cloned_agent_state + .is_running + .store(false, std::sync::atomic::Ordering::Relaxed); }); Ok(agent_state) } diff --git a/apps/desktop/desktop_native/macos_provider/.gitignore b/apps/desktop/desktop_native/macos_provider/.gitignore new file mode 100644 index 00000000000..73f0a6381d8 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/.gitignore @@ -0,0 +1 @@ +BitwardenMacosProviderFFI.xcframework diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml new file mode 100644 index 00000000000..ff7408d6d44 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "macos_provider" +license = "GPL-3.0" +version = "0.0.0" +edition = "2021" +publish = false + +[[bin]] +name = "uniffi-bindgen" +path = "uniffi-bindgen.rs" + +[lib] +crate-type = ["staticlib", "cdylib"] +bench = false + +[dependencies] +desktop_core = { path = "../core" } +futures = "=0.3.31" +log = "0.4.22" +serde = { version = "1.0.205", features = ["derive"] } +serde_json = "1.0.122" +tokio = { version = "1.39.2", features = ["sync"] } +tokio-util = "0.7.11" +uniffi = { version = "0.28.3", features = ["cli"] } + +[target.'cfg(target_os = "macos")'.dependencies] +oslog = "0.2.0" + +[build-dependencies] +uniffi = { version = "0.28.3", features = ["build"] } diff --git a/apps/desktop/desktop_native/macos_provider/build.sh b/apps/desktop/desktop_native/macos_provider/build.sh new file mode 100755 index 00000000000..21e2e045af4 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/build.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +cd "$(dirname "$0")" + +rm -r BitwardenMacosProviderFFI.xcframework +rm -r tmp + +mkdir -p ./tmp/target/universal-darwin/release/ + + +cargo build --package macos_provider --target aarch64-apple-darwin --release +cargo build --package macos_provider --target x86_64-apple-darwin --release + +# Create universal libraries +lipo -create ../target/aarch64-apple-darwin/release/libmacos_provider.a \ + ../target/x86_64-apple-darwin/release/libmacos_provider.a \ + -output ./tmp/target/universal-darwin/release/libmacos_provider.a + +# Generate swift bindings +cargo run --bin uniffi-bindgen --features uniffi/cli generate \ + ../target/aarch64-apple-darwin/release/libmacos_provider.dylib \ + --library \ + --language swift \ + --no-format \ + --out-dir tmp/bindings + +# Move generated swift bindings +mkdir -p ../../macos/autofill-extension/ +mv ./tmp/bindings/*.swift ../../macos/autofill-extension/ + +# Massage the generated files to fit xcframework +mkdir tmp/Headers +mv ./tmp/bindings/*.h ./tmp/Headers/ +cat ./tmp/bindings/*.modulemap > ./tmp/Headers/module.modulemap + +# Build xcframework +xcodebuild -create-xcframework \ + -library ./tmp/target/universal-darwin/release/libmacos_provider.a \ + -headers ./tmp/Headers \ + -output ./BitwardenMacosProviderFFI.xcframework + +# Cleanup temporary files +rm -r tmp diff --git a/apps/desktop/desktop_native/macos_provider/src/assertion.rs b/apps/desktop/desktop_native/macos_provider/src/assertion.rs new file mode 100644 index 00000000000..762ceaaed48 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/src/assertion.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; + +use crate::{BitwardenError, Callback, UserVerification}; + +#[derive(uniffi::Record, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyAssertionRequest { + rp_id: String, + credential_id: Vec, + user_name: String, + user_handle: Vec, + record_identifier: Option, + client_data_hash: Vec, + user_verification: UserVerification, +} + +#[derive(uniffi::Record, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyAssertionResponse { + rp_id: String, + user_handle: Vec, + signature: Vec, + client_data_hash: Vec, + authenticator_data: Vec, + credential_id: Vec, +} + +#[uniffi::export(with_foreign)] +pub trait PreparePasskeyAssertionCallback: Send + Sync { + fn on_complete(&self, credential: PasskeyAssertionResponse); + fn on_error(&self, error: BitwardenError); +} + +impl Callback for Arc { + fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error> { + let credential = serde_json::from_value(credential)?; + PreparePasskeyAssertionCallback::on_complete(self.as_ref(), credential); + Ok(()) + } + + fn error(&self, error: BitwardenError) { + PreparePasskeyAssertionCallback::on_error(self.as_ref(), error); + } +} diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs new file mode 100644 index 00000000000..5623436d874 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs @@ -0,0 +1,205 @@ +#![cfg(target_os = "macos")] + +use std::{ + collections::HashMap, + sync::{atomic::AtomicU32, Arc, Mutex}, + time::Instant, +}; + +use futures::FutureExt; +use log::{error, info}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +uniffi::setup_scaffolding!(); + +mod assertion; +mod registration; + +use assertion::{PasskeyAssertionRequest, PreparePasskeyAssertionCallback}; +use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback}; + +#[derive(uniffi::Enum, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum UserVerification { + Preferred, + Required, + Discouraged, +} + +#[derive(Debug, uniffi::Error, Serialize, Deserialize)] +pub enum BitwardenError { + Internal(String), +} + +// TODO: These have to be named differently than the actual Uniffi traits otherwise +// the generated code will lead to ambiguous trait implementations +// These are only used internally, so it doesn't matter that much +trait Callback: Send + Sync { + fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error>; + fn error(&self, error: BitwardenError); +} + +#[derive(uniffi::Object)] +pub struct MacOSProviderClient { + to_server_send: tokio::sync::mpsc::Sender, + + // We need to keep track of the callbacks so we can call them when we receive a response + response_callbacks_counter: AtomicU32, + #[allow(clippy::type_complexity)] + response_callbacks_queue: Arc, Instant)>>>, +} + +#[uniffi::export] +impl MacOSProviderClient { + #[uniffi::constructor] + pub fn connect() -> Self { + let _ = oslog::OsLogger::new("com.bitwarden.desktop.autofill-extension") + .level_filter(log::LevelFilter::Trace) + .init(); + + let (from_server_send, mut from_server_recv) = tokio::sync::mpsc::channel(32); + let (to_server_send, to_server_recv) = tokio::sync::mpsc::channel(32); + + let client = MacOSProviderClient { + to_server_send, + response_callbacks_counter: AtomicU32::new(0), + response_callbacks_queue: Arc::new(Mutex::new(HashMap::new())), + }; + + let path = desktop_core::ipc::path("autofill"); + + let queue = client.response_callbacks_queue.clone(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Can't create runtime"); + + rt.spawn( + desktop_core::ipc::client::connect(path, from_server_send, to_server_recv) + .map(|r| r.map_err(|e| e.to_string())), + ); + + rt.block_on(async move { + while let Some(message) = from_server_recv.recv().await { + match serde_json::from_str::(&message) { + Ok(SerializedMessage::Command(CommandMessage::Connected)) => { + info!("Connected to server"); + } + Ok(SerializedMessage::Command(CommandMessage::Disconnected)) => { + info!("Disconnected from server"); + } + Ok(SerializedMessage::Message { + sequence_number, + value, + }) => match queue.lock().unwrap().remove(&sequence_number) { + Some((cb, request_start_time)) => { + info!( + "Time to process request: {:?}", + request_start_time.elapsed() + ); + match value { + Ok(value) => { + if let Err(e) = cb.complete(value) { + error!("Error deserializing message: {e}"); + } + } + Err(e) => { + error!("Error processing message: {e:?}"); + cb.error(e) + } + } + } + None => { + error!("No callback found for sequence number: {sequence_number}") + } + }, + Err(e) => { + error!("Error deserializing message: {e}"); + } + }; + } + }); + }); + + client + } + + pub fn prepare_passkey_registration( + &self, + request: PasskeyRegistrationRequest, + callback: Arc, + ) { + self.send_message(request, Box::new(callback)); + } + + pub fn prepare_passkey_assertion( + &self, + request: PasskeyAssertionRequest, + callback: Arc, + ) { + self.send_message(request, Box::new(callback)); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "command", rename_all = "camelCase")] +enum CommandMessage { + Connected, + Disconnected, +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged, rename_all = "camelCase")] +enum SerializedMessage { + Command(CommandMessage), + Message { + sequence_number: u32, + value: Result, + }, +} + +impl MacOSProviderClient { + fn add_callback(&self, callback: Box) -> u32 { + let sequence_number = self + .response_callbacks_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + self.response_callbacks_queue + .lock() + .unwrap() + .insert(sequence_number, (callback, Instant::now())); + + sequence_number + } + + fn send_message( + &self, + message: impl Serialize + DeserializeOwned, + callback: Box, + ) { + let sequence_number = self.add_callback(callback); + + let message = serde_json::to_string(&SerializedMessage::Message { + sequence_number, + value: Ok(serde_json::to_value(message).unwrap()), + }) + .expect("Can't serialize message"); + + if let Err(e) = self.to_server_send.blocking_send(message) { + // Make sure we remove the callback from the queue if we can't send the message + if let Some((cb, _)) = self + .response_callbacks_queue + .lock() + .unwrap() + .remove(&sequence_number) + { + cb.error(BitwardenError::Internal(format!( + "Error sending message: {}", + e + ))); + } + } + } +} diff --git a/apps/desktop/desktop_native/macos_provider/src/registration.rs b/apps/desktop/desktop_native/macos_provider/src/registration.rs new file mode 100644 index 00000000000..d484af58b6c --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/src/registration.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; + +use crate::{BitwardenError, Callback, UserVerification}; + +#[derive(uniffi::Record, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyRegistrationRequest { + rp_id: String, + user_name: String, + user_handle: Vec, + client_data_hash: Vec, + user_verification: UserVerification, + supported_algorithms: Vec, +} + +#[derive(uniffi::Record, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyRegistrationResponse { + rp_id: String, + client_data_hash: Vec, + credential_id: Vec, + attestation_object: Vec, +} + +#[uniffi::export(with_foreign)] +pub trait PreparePasskeyRegistrationCallback: Send + Sync { + fn on_complete(&self, credential: PasskeyRegistrationResponse); + fn on_error(&self, error: BitwardenError); +} + +impl Callback for Arc { + fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error> { + let credential = serde_json::from_value(credential)?; + PreparePasskeyRegistrationCallback::on_complete(self.as_ref(), credential); + Ok(()) + } + + fn error(&self, error: BitwardenError) { + PreparePasskeyRegistrationCallback::on_error(self.as_ref(), error); + } +} diff --git a/apps/desktop/desktop_native/macos_provider/uniffi-bindgen.rs b/apps/desktop/desktop_native/macos_provider/uniffi-bindgen.rs new file mode 100644 index 00000000000..f6cff6cf1d9 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/apps/desktop/desktop_native/macos_provider/uniffi.toml b/apps/desktop/desktop_native/macos_provider/uniffi.toml new file mode 100644 index 00000000000..ba696b8ec15 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/uniffi.toml @@ -0,0 +1,4 @@ +[bindings.swift] +ffi_module_name = "BitwardenMacosProviderFFI" +module_name = "BitwardenMacosProvider" +generate_immutable_records = true diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index bf7701a6566..8e19d62c1e6 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -16,11 +16,13 @@ manual_test = [] [dependencies] base64 = "=0.22.1" hex = "=0.4.3" -anyhow = "=1.0.93" +anyhow = "=1.0.94" desktop_core = { path = "../core" } napi = { version = "=2.16.13", features = ["async"] } -napi-derive = "=2.16.12" -tokio = { version = "=1.40.0" } +napi-derive = "=2.16.13" +serde = { version = "1.0.209", features = ["derive"] } +serde_json = "1.0.127" +tokio = { version = "=1.41.1" } tokio-util = "=0.7.12" tokio-stream = "=0.1.15" @@ -28,4 +30,4 @@ tokio-stream = "=0.1.15" windows-registry = "=0.3.0" [build-dependencies] -napi-build = "=2.1.3" +napi-build = "=2.1.4" diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 6d1a7b8abbc..997e951c89e 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -6,8 +6,6 @@ export declare namespace passwords { /** Fetch the stored password from the keychain. */ export function getPassword(service: string, account: string): Promise - /** Fetch the stored password from the keychain that was stored with Keytar. */ - export function getPasswordKeytar(service: string, account: string): Promise /** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */ export function setPassword(service: string, account: string, password: string): Promise /** Delete the stored password from the keychain. */ @@ -69,12 +67,13 @@ export declare namespace sshagent { status: SshKeyImportStatus sshKey?: SshKey } - export function serve(callback: (err: Error | null, arg: string) => any): Promise + export function serve(callback: (err: Error | null, arg0: string | undefined | null, arg1: boolean, arg2: string) => any): Promise export function stop(agentState: SshAgentState): void + export function isRunning(agentState: SshAgentState): boolean export function setKeys(agentState: SshAgentState, newKeys: Array): void export function lock(agentState: SshAgentState): void export function importKey(encodedKey: string, password: string): SshKeyImportResult - export function generateKeypair(keyAlgorithm: string): Promise + export function clearKeys(agentState: SshAgentState): void export class SshAgentState { } } export declare namespace processisolations { @@ -109,6 +108,8 @@ export declare namespace ipc { * @param callback This function will be called whenever a message is received from a client. */ static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise + /** Return the path to the IPC server. */ + getPath(): string /** Stop the IPC server. */ stop(): void /** @@ -120,3 +121,61 @@ export declare namespace ipc { send(message: string): number } } +export declare namespace autofill { + export function runCommand(value: string): Promise + export const enum UserVerification { + Preferred = 'preferred', + Required = 'required', + Discouraged = 'discouraged' + } + export interface PasskeyRegistrationRequest { + rpId: string + userName: string + userHandle: Array + clientDataHash: Array + userVerification: UserVerification + supportedAlgorithms: Array + } + export interface PasskeyRegistrationResponse { + rpId: string + clientDataHash: Array + credentialId: Array + attestationObject: Array + } + export interface PasskeyAssertionRequest { + rpId: string + credentialId: Array + userName: string + userHandle: Array + recordIdentifier?: string + clientDataHash: Array + userVerification: UserVerification + } + export interface PasskeyAssertionResponse { + rpId: string + userHandle: Array + signature: Array + clientDataHash: Array + authenticatorData: Array + credentialId: Array + } + export class IpcServer { + /** + * Create and start the IPC server without blocking. + * + * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. + * @param callback This function will be called whenever a message is received from a client. + */ + static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void): Promise + /** Return the path to the IPC server. */ + getPath(): string + /** Stop the IPC server. */ + stop(): void + completeRegistration(clientId: number, sequenceNumber: number, response: PasskeyRegistrationResponse): number + completeAssertion(clientId: number, sequenceNumber: number, response: PasskeyAssertionResponse): number + completeError(clientId: number, sequenceNumber: number, error: string): number + } +} +export declare namespace crypto { + export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise +} diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index a0cfee8e1a0..acfd0dffb89 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -1,209 +1,132 @@ -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') +const { existsSync } = require("fs"); +const { join } = require("path"); -const { platform, arch } = process +const { platform, arch } = process; -let nativeBinding = null -let localFileExisted = false -let loadError = null +let nativeBinding = null; +let localFileExisted = false; +let loadError = null; -function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - return readFileSync('/usr/bin/ldd', 'utf8').includes('musl') - } catch (e) { - return true +function loadFirstAvailable(localFiles, nodeModule) { + for (const localFile of localFiles) { + if (existsSync(join(__dirname, localFile))) { + return require(`./${localFile}`); } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime } + + require(nodeModule); } switch (platform) { - case 'android': + case "android": switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'desktop_napi.android-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.android-arm64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'desktop_napi.android-arm-eabi.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.android-arm-eabi.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.android-arm64.node"], + "@bitwarden/desktop-napi-android-arm64", + ); + break; + case "arm": + nativeBinding = loadFirstAvailable( + ["desktop_napi.android-arm.node"], + "@bitwarden/desktop-napi-android-arm", + ); + break; default: - throw new Error(`Unsupported architecture on Android ${arch}`) + throw new Error(`Unsupported architecture on Android ${arch}`); } - break - case 'win32': + break; + case "win32": switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.win32-x64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.win32-x64-msvc.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.win32-ia32-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.win32-ia32-msvc.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.win32-arm64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.win32-arm64-msvc.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break + case "x64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.win32-x64-msvc.node"], + "@bitwarden/desktop-napi-win32-x64-msvc", + ); + break; + case "ia32": + nativeBinding = loadFirstAvailable( + ["desktop_napi.win32-ia32-msvc.node"], + "@bitwarden/desktop-napi-win32-ia32-msvc", + ); + break; + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.win32-arm64-msvc.node"], + "@bitwarden/desktop-napi-win32-arm64-msvc", + ); + break; default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) + throw new Error(`Unsupported architecture on Windows: ${arch}`); } - break - case 'darwin': + break; + case "darwin": switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'desktop_napi.darwin-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.darwin-x64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.darwin-arm64.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.darwin-arm64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-darwin-arm64') - } - } catch (e) { - loadError = e - } - break + case "x64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.darwin-x64.node"], + "@bitwarden/desktop-napi-darwin-x64", + ); + break; + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.darwin-arm64.node"], + "@bitwarden/desktop-napi-darwin-arm64", + ); + break; default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) + throw new Error(`Unsupported architecture on macOS: ${arch}`); } - break - case 'freebsd': - if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) - } - localFileExisted = existsSync(join(__dirname, 'desktop_napi.freebsd-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.freebsd-x64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-freebsd-x64') - } - } catch (e) { - loadError = e - } - break - case 'linux': + break; + case "freebsd": + nativeBinding = loadFirstAvailable( + ["desktop_napi.freebsd-x64.node"], + "@bitwarden/desktop-napi-freebsd-x64", + ); + break; + case "linux": switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.linux-x64-musl.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-linux-x64-musl') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.linux-arm64-musl.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.linux-arm-gnueabihf.node') - ) + case "x64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.linux-x64-musl.node", "desktop_napi.linux-x64-gnu.node"], + "@bitwarden/desktop-napi-linux-x64-musl", + ); + break; + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.linux-arm64-musl.node", "desktop_napi.linux-arm64-gnu.node"], + "@bitwarden/desktop-napi-linux-arm64-musl", + ); + break; + case "arm": + nativeBinding = loadFirstAvailable( + ["desktop_napi.linux-arm-musl.node", "desktop_napi.linux-arm-gnu.node"], + "@bitwarden/desktop-napi-linux-arm-musl", + ); + localFileExisted = existsSync(join(__dirname, "desktop_napi.linux-arm-gnueabihf.node")); try { if (localFileExisted) { - nativeBinding = require('./desktop_napi.linux-arm-gnueabihf.node') + nativeBinding = require("./desktop_napi.linux-arm-gnueabihf.node"); } else { - nativeBinding = require('@bitwarden/desktop-napi-linux-arm-gnueabihf') + nativeBinding = require("@bitwarden/desktop-napi-linux-arm-gnueabihf"); } } catch (e) { - loadError = e + loadError = e; } - break + break; default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) + throw new Error(`Unsupported architecture on Linux: ${arch}`); } - break + break; default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`); } if (!nativeBinding) { if (loadError) { - throw loadError + throw loadError; } - throw new Error(`Failed to load native binding`) + throw new Error(`Failed to load native binding`); } -module.exports = nativeBinding +module.exports = nativeBinding; diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 60a8326a8e5..35566e16813 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -9,13 +9,7 @@ pub mod passwords { #[napi] pub async fn get_password(service: String, account: String) -> napi::Result { desktop_core::password::get_password(&service, &account) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Fetch the stored password from the keychain that was stored with Keytar. - #[napi] - pub async fn get_password_keytar(service: String, account: String) -> napi::Result { - desktop_core::password::get_password_keytar(&service, &account) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -27,6 +21,7 @@ pub mod passwords { password: String, ) -> napi::Result<()> { desktop_core::password::set_password(&service, &account, &password) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -34,13 +29,16 @@ pub mod passwords { #[napi] pub async fn delete_password(service: String, account: String) -> napi::Result<()> { desktop_core::password::delete_password(&service, &account) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } // Checks if the os secure storage is available #[napi] pub async fn is_available() -> napi::Result { - desktop_core::password::is_available().map_err(|e| napi::Error::from_reason(e.to_string())) + desktop_core::password::is_available() + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) } } @@ -81,6 +79,7 @@ pub mod biometrics { key_material.map(|m| m.into()), &iv_b64, ) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -90,10 +89,9 @@ pub mod biometrics { account: String, key_material: Option, ) -> napi::Result { - let result = - Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) - .map_err(|e| napi::Error::from_reason(e.to_string())); - result + Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) } /// Derives key material from biometric data. Returns a string encoded with a @@ -247,41 +245,53 @@ pub mod sshagent { #[napi] pub async fn serve( - callback: ThreadsafeFunction, + callback: ThreadsafeFunction<(Option, bool, String), CalleeHandled>, ) -> napi::Result { - let (auth_request_tx, mut auth_request_rx) = tokio::sync::mpsc::channel::<(u32, String)>(32); - let (auth_response_tx, auth_response_rx) = tokio::sync::broadcast::channel::<(u32, bool)>(32); + let (auth_request_tx, mut auth_request_rx) = + tokio::sync::mpsc::channel::(32); + let (auth_response_tx, auth_response_rx) = + tokio::sync::broadcast::channel::<(u32, bool)>(32); let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); tokio::spawn(async move { let _ = auth_response_rx; - while let Some((request_id, cipher_uuid)) = auth_request_rx.recv().await { - let cloned_request_id = request_id.clone(); - let cloned_cipher_uuid = cipher_uuid.clone(); + while let Some(request) = auth_request_rx.recv().await { let cloned_response_tx_arc = auth_response_tx_arc.clone(); let cloned_callback = callback.clone(); tokio::spawn(async move { - let request_id = cloned_request_id; - let cipher_uuid = cloned_cipher_uuid; let auth_response_tx_arc = cloned_response_tx_arc; let callback = cloned_callback; - let promise_result: Result, napi::Error> = - callback.call_async(Ok(cipher_uuid)).await; + let promise_result: Result, napi::Error> = callback + .call_async(Ok(( + request.cipher_id, + request.is_list, + request.process_name, + ))) + .await; match promise_result { Ok(promise_result) => match promise_result.await { Ok(result) => { - let _ = auth_response_tx_arc.lock().await.send((request_id, result)) + let _ = auth_response_tx_arc + .lock() + .await + .send((request.request_id, result)) .expect("should be able to send auth response to agent"); } Err(e) => { println!("[SSH Agent Native Module] calling UI callback promise was rejected: {}", e); - let _ = auth_response_tx_arc.lock().await.send((request_id, false)) + let _ = auth_response_tx_arc + .lock() + .await + .send((request.request_id, false)) .expect("should be able to send auth response to agent"); } }, Err(e) => { println!("[SSH Agent Native Module] calling UI callback could not create promise: {}", e); - let _ = auth_response_tx_arc.lock().await.send((request_id, false)) + let _ = auth_response_tx_arc + .lock() + .await + .send((request.request_id, false)) .expect("should be able to send auth response to agent"); } } @@ -307,6 +317,12 @@ pub mod sshagent { Ok(()) } + #[napi] + pub fn is_running(agent_state: &mut SshAgentState) -> bool { + let bitwarden_agent_state = agent_state.state.clone(); + bitwarden_agent_state.is_running() + } + #[napi] pub fn set_keys( agent_state: &mut SshAgentState, @@ -340,11 +356,11 @@ pub mod sshagent { } #[napi] - pub async fn generate_keypair(key_algorithm: String) -> napi::Result { - desktop_core::ssh_agent::generator::generate_keypair(key_algorithm) - .await + pub fn clear_keys(agent_state: &mut SshAgentState) -> napi::Result<()> { + let bitwarden_agent_state = &mut agent_state.state; + bitwarden_agent_state + .clear_keys() .map_err(|e| napi::Error::from_reason(e.to_string())) - .map(|k| k.into()) } } @@ -383,8 +399,8 @@ pub mod powermonitors { .await .map_err(|e| napi::Error::from_reason(e.to_string()))?; tokio::spawn(async move { - while let Some(message) = rx.recv().await { - callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking); + while let Some(()) = rx.recv().await { + callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking); } }); Ok(()) @@ -487,6 +503,12 @@ pub mod ipc { Ok(IpcServer { server }) } + /// Return the path to the IPC server. + #[napi] + pub fn get_path(&self) -> String { + self.server.path.to_string_lossy().to_string() + } + /// Stop the IPC server. #[napi] pub fn stop(&self) -> napi::Result<()> { @@ -510,3 +532,276 @@ pub mod ipc { } } } + +#[napi] +pub mod autofill { + use desktop_core::ipc::server::{Message, MessageType}; + use napi::threadsafe_function::{ + ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, + }; + use serde::{de::DeserializeOwned, Deserialize, Serialize}; + + #[napi] + pub async fn run_command(value: String) -> napi::Result { + desktop_core::autofill::run_command(value) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[derive(Debug, serde::Serialize, serde:: Deserialize)] + pub enum BitwardenError { + Internal(String), + } + + #[napi(string_enum)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub enum UserVerification { + #[napi(value = "preferred")] + Preferred, + #[napi(value = "required")] + Required, + #[napi(value = "discouraged")] + Discouraged, + } + + #[derive(Serialize, Deserialize)] + #[serde(bound = "T: Serialize + DeserializeOwned")] + pub struct PasskeyMessage { + pub sequence_number: u32, + pub value: Result, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyRegistrationRequest { + pub rp_id: String, + pub user_name: String, + pub user_handle: Vec, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + pub supported_algorithms: Vec, + } + + #[napi(object)] + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyRegistrationResponse { + pub rp_id: String, + pub client_data_hash: Vec, + pub credential_id: Vec, + pub attestation_object: Vec, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionRequest { + pub rp_id: String, + pub credential_id: Vec, + pub user_name: String, + pub user_handle: Vec, + pub record_identifier: Option, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + } + + #[napi(object)] + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionResponse { + pub rp_id: String, + pub user_handle: Vec, + pub signature: Vec, + pub client_data_hash: Vec, + pub authenticator_data: Vec, + pub credential_id: Vec, + } + + #[napi] + pub struct IpcServer { + server: desktop_core::ipc::server::Server, + } + + #[napi] + impl IpcServer { + /// Create and start the IPC server without blocking. + /// + /// @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. + /// @param callback This function will be called whenever a message is received from a client. + #[napi(factory)] + pub async fn listen( + name: String, + // Ideally we'd have a single callback that has an enum containing the request values, + // but NAPI doesn't support that just yet + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void" + )] + registration_callback: ThreadsafeFunction< + (u32, u32, PasskeyRegistrationRequest), + ErrorStrategy::CalleeHandled, + >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void" + )] + assertion_callback: ThreadsafeFunction< + (u32, u32, PasskeyAssertionRequest), + ErrorStrategy::CalleeHandled, + >, + ) -> napi::Result { + let (send, mut recv) = tokio::sync::mpsc::channel::(32); + tokio::spawn(async move { + while let Some(Message { + client_id, + kind, + message, + }) = recv.recv().await + { + match kind { + // TODO: We're ignoring the connection and disconnection messages for now + MessageType::Connected | MessageType::Disconnected => continue, + MessageType::Message => { + let Some(message) = message else { + println!("[ERROR] Message is empty"); + continue; + }; + + match serde_json::from_str::>( + &message, + ) { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value)) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + + assertion_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + println!("[ERROR] Error deserializing message1: {e}"); + } + } + + match serde_json::from_str::>( + &message, + ) { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value)) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + registration_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + println!("[ERROR] Error deserializing message2: {e}"); + } + } + + println!("[ERROR] Received an unknown message2: {message:?}"); + } + } + } + }); + + let path = desktop_core::ipc::path(&name); + + let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { + napi::Error::from_reason(format!( + "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" + )) + })?; + + Ok(IpcServer { server }) + } + + /// Return the path to the IPC server. + #[napi] + pub fn get_path(&self) -> String { + self.server.path.to_string_lossy().to_string() + } + + /// Stop the IPC server. + #[napi] + pub fn stop(&self) -> napi::Result<()> { + self.server.stop(); + Ok(()) + } + + #[napi] + pub fn complete_registration( + &self, + client_id: u32, + sequence_number: u32, + response: PasskeyRegistrationResponse, + ) -> napi::Result { + let message = PasskeyMessage { + sequence_number, + value: Ok(response), + }; + self.send(client_id, serde_json::to_string(&message).unwrap()) + } + + #[napi] + pub fn complete_assertion( + &self, + client_id: u32, + sequence_number: u32, + response: PasskeyAssertionResponse, + ) -> napi::Result { + let message = PasskeyMessage { + sequence_number, + value: Ok(response), + }; + self.send(client_id, serde_json::to_string(&message).unwrap()) + } + + #[napi] + pub fn complete_error( + &self, + client_id: u32, + sequence_number: u32, + error: String, + ) -> napi::Result { + let message: PasskeyMessage<()> = PasskeyMessage { + sequence_number, + value: Err(BitwardenError::Internal(error)), + }; + self.send(client_id, serde_json::to_string(&message).unwrap()) + } + + // TODO: Add a way to send a message to a specific client? + fn send(&self, _client_id: u32, message: String) -> napi::Result { + self.server + .send(message) + .map_err(|e| { + napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) + }) + // NAPI doesn't support u64 or usize, so we need to convert to u32 + .map(|u| u32::try_from(u).unwrap_or_default()) + } + } +} + +#[napi] +pub mod crypto { + use napi::bindgen_prelude::Buffer; + + #[napi] + pub async fn argon2( + secret: Buffer, + salt: Buffer, + iterations: u32, + memory: u32, + parallelism: u32, + ) -> napi::Result { + desktop_core::crypto::argon2(&secret, &salt, iterations, memory, parallelism) + .map_err(|e| napi::Error::from_reason(e.to_string())) + .map(|v| v.to_vec()) + .map(Buffer::from) + } +} diff --git a/apps/desktop/desktop_native/napi/src/registry/windows.rs b/apps/desktop/desktop_native/napi/src/registry/windows.rs index 481dfb5dc49..aeb381dafda 100644 --- a/apps/desktop/desktop_native/napi/src/registry/windows.rs +++ b/apps/desktop/desktop_native/napi/src/registry/windows.rs @@ -13,7 +13,7 @@ pub fn create_key(key: &str, subkey: &str, value: &str) -> Result<()> { let key = convert_key(key)?; let subkey = key.create(subkey)?; - + const DEFAULT: &str = ""; subkey.set_string(DEFAULT, value)?; diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml new file mode 100644 index 00000000000..04e332b8db3 --- /dev/null +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +license = "GPL-3.0" +name = "desktop_objc" +version = "0.0.0" +publish = false + +[features] +default = [] + +[dependencies] +anyhow = "=1.0.94" +thiserror = "=1.0.69" +tokio = "1.39.1" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "=0.10.0" + +[build-dependencies] +cc = "1.2.4" +glob = "0.3.1" diff --git a/apps/desktop/desktop_native/objc/build.rs b/apps/desktop/desktop_native/objc/build.rs new file mode 100644 index 00000000000..1f9be6bc02f --- /dev/null +++ b/apps/desktop/desktop_native/objc/build.rs @@ -0,0 +1,21 @@ +#[cfg(target_os = "macos")] +fn main() { + use glob::glob; + let mut builder = cc::Build::new(); + + // Auto compile all .m files in the src/native directory + for entry in glob("src/native/**/*.m").expect("Failed to read glob pattern") { + let path = entry.expect("Failed to read glob entry"); + builder.file(path.clone()); + println!("cargo::rerun-if-changed={}", path.display()); + } + + builder + .flag("-fobjc-arc") // Enable Auto Reference Counting (ARC) + .compile("autofill"); +} + +#[cfg(not(target_os = "macos"))] +fn main() { + // Crate is only supported on macOS +} diff --git a/apps/desktop/desktop_native/objc/src/lib.rs b/apps/desktop/desktop_native/objc/src/lib.rs new file mode 100644 index 00000000000..eb969cb5f56 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/lib.rs @@ -0,0 +1,124 @@ +#![cfg(target_os = "macos")] + +use std::{ + ffi::{c_char, CStr, CString}, + os::raw::c_void, +}; + +use anyhow::{Context, Result}; + +#[repr(C)] +pub struct ObjCString { + value: *const c_char, + size: usize, +} + +#[repr(C)] +pub struct CommandContext { + tx: Option>, +} + +impl CommandContext { + pub fn new() -> (Self, tokio::sync::oneshot::Receiver) { + let (tx, rx) = tokio::sync::oneshot::channel::(); + + (CommandContext { tx: Some(tx) }, rx) + } + + pub fn send(&mut self, value: String) -> Result<()> { + let tx = self.tx.take().context( + "Failed to take Sender from CommandContext. Has this context already returned once?", + )?; + + tx.send(value).map_err(|_| { + anyhow::anyhow!("Failed to send ObjCString from CommandContext to Rust code") + })?; + + Ok(()) + } + + pub fn as_ptr(&mut self) -> *mut c_void { + self as *mut Self as *mut c_void + } +} + +impl TryFrom for String { + type Error = anyhow::Error; + + fn try_from(value: ObjCString) -> Result { + let c_str = unsafe { CStr::from_ptr(value.value) }; + let str = c_str + .to_str() + .context("Failed to convert ObjC output string to &str for use in Rust")?; + + Ok(str.to_owned()) + } +} + +impl Drop for ObjCString { + fn drop(&mut self) { + unsafe { + objc::freeObjCString(self); + } + } +} + +mod objc { + use std::os::raw::c_void; + + use super::*; + + extern "C" { + pub fn runCommand(context: *mut c_void, value: *const c_char); + pub fn freeObjCString(value: &ObjCString); + } + + /// This function is called from the ObjC code to return the output of the command + #[no_mangle] + pub extern "C" fn commandReturn(context: &mut CommandContext, value: ObjCString) -> bool { + let value: String = match value.try_into() { + Ok(value) => value, + Err(e) => { + println!( + "Error: Failed to convert ObjCString to Rust string during commandReturn: {}", + e + ); + + return false; + } + }; + + match context.send(value) { + Ok(_) => 0, + Err(e) => { + println!( + "Error: Failed to return ObjCString from ObjC code to Rust code: {}", + e + ); + + return false; + } + }; + + true + } +} + +pub async fn run_command(input: String) -> Result { + // Convert input to type that can be passed to ObjC code + let c_input = CString::new(input) + .context("Failed to convert Rust input string to a CString for use in call to ObjC code")?; + + let (mut context, rx) = CommandContext::new(); + + // Call ObjC code + unsafe { objc::runCommand(context.as_ptr(), c_input.as_ptr()) }; + + // Convert output from ObjC code to Rust string + let objc_output = rx.await?; + + // Convert output from ObjC code to Rust string + // let objc_output = output.try_into()?; + + Ok(objc_output) +} diff --git a/apps/desktop/desktop_native/objc/src/native/.clangd b/apps/desktop/desktop_native/objc/src/native/.clangd new file mode 100644 index 00000000000..5369bebcdc0 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-fobjc-arc] diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h new file mode 100644 index 00000000000..e3e3c7969b6 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h @@ -0,0 +1,8 @@ +#ifndef STATUS_H +#define STATUS_H + +#import + +void status(void *context, NSDictionary *params); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m new file mode 100644 index 00000000000..8811ffc6f0c --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m @@ -0,0 +1,57 @@ +#import +#import +#import +#import "../../interop.h" +#import "status.h" + +void storeState(void (^callback)(ASCredentialIdentityStoreState*)) { + if (@available(macos 11, *)) { + ASCredentialIdentityStore *store = [ASCredentialIdentityStore sharedStore]; + [store getCredentialIdentityStoreStateWithCompletion:^(ASCredentialIdentityStoreState * _Nonnull state) { + callback(state); + }]; + } else { + callback(nil); + } +} + +BOOL fido2Supported() { + if (@available(macos 14, *)) { + return YES; + } else { + return NO; + } +} + +BOOL passwordSupported() { + if (@available(macos 11, *)) { + return YES; + } else { + return NO; + } +} + +void status(void* context, __attribute__((unused)) NSDictionary *params) { + storeState(^(ASCredentialIdentityStoreState *state) { + BOOL enabled = NO; + BOOL supportsIncremental = NO; + + if (state != nil) { + enabled = state.isEnabled; + supportsIncremental = state.supportsIncrementalUpdates; + } + + _return(context, + _success(@{ + @"support": @{ + @"fido2": @(fido2Supported()), + @"password": @(passwordSupported()), + @"incrementalUpdates": @(supportsIncremental), + }, + @"state": @{ + @"enabled": @(enabled), + } + }) + ); + }); +} diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h new file mode 100644 index 00000000000..4eb39ff24d6 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h @@ -0,0 +1,8 @@ +#ifndef SYNC_H +#define SYNC_H + +#import + +void runSync(void *context, NSDictionary *params); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m new file mode 100644 index 00000000000..8b73635a7ca --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m @@ -0,0 +1,59 @@ +#import +#import +#import +#import +#import +#import +#import "../../utils.h" +#import "../../interop.h" +#import "sync.h" + +// 'run' is added to the name because it clashes with internal macOS function +void runSync(void* context, NSDictionary *params) { + NSArray *credentials = params[@"credentials"]; + + // Map credentials to ASPasswordCredential objects + NSMutableArray *mappedCredentials = [NSMutableArray arrayWithCapacity:credentials.count]; + for (NSDictionary *credential in credentials) { + NSString *type = credential[@"type"]; + + if ([type isEqualToString:@"password"]) { + NSString *cipherId = credential[@"cipherId"]; + NSString *uri = credential[@"uri"]; + NSString *username = credential[@"username"]; + + ASCredentialServiceIdentifier *serviceId = [[ASCredentialServiceIdentifier alloc] + initWithIdentifier:uri type:ASCredentialServiceIdentifierTypeURL]; + ASPasswordCredentialIdentity *credential = [[ASPasswordCredentialIdentity alloc] + initWithServiceIdentifier:serviceId user:username recordIdentifier:cipherId]; + + [mappedCredentials addObject:credential]; + } + + if ([type isEqualToString:@"fido2"]) { + NSString *cipherId = credential[@"cipherId"]; + NSString *rpId = credential[@"rpId"]; + NSString *userName = credential[@"userName"]; + NSData *credentialId = decodeBase64URL(credential[@"credentialId"]); + NSData *userHandle = decodeBase64URL(credential[@"userHandle"]); + + ASPasskeyCredentialIdentity *credential = [[ASPasskeyCredentialIdentity alloc] + initWithRelyingPartyIdentifier:rpId + userName:userName + credentialID:credentialId + userHandle:userHandle + recordIdentifier:cipherId]; + + [mappedCredentials addObject:credential]; + } + } + + [ASCredentialIdentityStore.sharedStore replaceCredentialIdentityEntries:mappedCredentials + completion:^(__attribute__((unused)) BOOL success, NSError * _Nullable error) { + if (error) { + return _return(context, _error_er(error)); + } + + _return(context, _success(@{@"added": @([mappedCredentials count])})); + }]; +} diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h new file mode 100644 index 00000000000..fba5f626863 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h @@ -0,0 +1,8 @@ +#ifndef RUN_AUTOFILL_COMMAND_H +#define RUN_AUTOFILL_COMMAND_H + +#import + +void runAutofillCommand(void* context, NSDictionary *input); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m new file mode 100644 index 00000000000..46b188139bc --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m @@ -0,0 +1,20 @@ +#import +#import "commands/sync.h" +#import "commands/status.h" +#import "../interop.h" +#import "../utils.h" +#import "run_autofill_command.h" + +void runAutofillCommand(void* context, NSDictionary *input) { + NSString *command = input[@"command"]; + NSDictionary *params = input[@"params"]; + + if ([command isEqual:@"status"]) { + return status(context, params); + } else if ([command isEqual:@"sync"]) { + return runSync(context, params); + } + + _return(context, _error([NSString stringWithFormat:@"Unknown command: %@", command])); +} + diff --git a/apps/desktop/desktop_native/objc/src/native/interop.h b/apps/desktop/desktop_native/objc/src/native/interop.h new file mode 100644 index 00000000000..584fe547a9d --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/interop.h @@ -0,0 +1,47 @@ +#ifndef INTEROP_H +#define INTEROP_H + +#import + +// Tips for developing Objective-C code: +// - Use the `NSLog` function to log messages to the system log +// - Example: +// NSLog(@"An example log: %@", someVariable); +// - Use the `@try` and `@catch` directives to catch exceptions + +#if !__has_feature(objc_arc) + // Auto Reference Counting makes memory management easier for Objective-C objects + // Regular C objects still need to be managed manually + #error ARC must be enabled! +#endif + +/// [Shared with Rust] +/// Simple struct to hold a C-string and its length +/// This is used to return strings created in Objective-C to Rust +/// so that Rust can free the memory when it's done with the string +struct ObjCString +{ + char *value; + size_t size; +}; + +/// [Defined in Rust] +/// External function callable from Objective-C to return a string to Rust +extern bool commandReturn(void *context, struct ObjCString output); + +/// [Callable from Rust] +/// Frees the memory allocated for an ObjCString +void freeObjCString(struct ObjCString *value); + +// --- Helper functions to convert between Objective-C and Rust types --- + +NSString *_success(NSDictionary *value); +NSString *_error(NSString *error); +NSString *_error_er(NSError *error); +NSString *_error_ex(NSException *error); +void _return(void *context, NSString *output); + +struct ObjCString nsStringToObjCString(NSString *string); +NSString *cStringToNSString(char *string); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/interop.m b/apps/desktop/desktop_native/objc/src/native/interop.m new file mode 100644 index 00000000000..dc41eb52d76 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/interop.m @@ -0,0 +1,71 @@ +#import "interop.h" +#import "utils.h" + +/// [Callable from Rust] +/// Frees the memory allocated for an ObjCString +void freeObjCString(struct ObjCString *value) { + free(value->value); +} + +// --- Helper functions to convert between Objective-C and Rust types --- + +NSString *_success(NSDictionary *value) { + NSDictionary *wrapper = @{@"type": @"success", @"value": value}; + NSError *jsonError = nil; + NSString *toReturn = serializeJson(wrapper, jsonError); + + if (jsonError) { + // Manually format message since there seems to be an issue with the JSON serialization + return [NSString stringWithFormat:@"{\"type\": \"error\", \"error\": \"Error occurred while serializing error: %@\"}", jsonError]; + } + + return toReturn; +} + +NSString *_error(NSString *error) { + NSDictionary *errorDictionary = @{@"type": @"error", @"error": error}; + NSError *jsonError = nil; + NSString *toReturn = serializeJson(errorDictionary, jsonError); + + if (jsonError) { + // Manually format message since there seems to be an issue with the JSON serialization + return [NSString stringWithFormat:@"{\"type\": \"error\", \"error\": \"Error occurred while serializing error: %@\"}", jsonError]; + } + + return toReturn; +} + +NSString *_error_er(NSError *error) { + return _error([error localizedDescription]); +} + +NSString *_error_ex(NSException *error) { + return _error([NSString stringWithFormat:@"%@ (%@): %@", error.name, error.reason, [error callStackSymbols]]); +} + +void _return(void* context, NSString *output) { + if (!commandReturn(context, nsStringToObjCString(output))) { + NSLog(@"Error: Failed to return command output"); + // NOTE: This will most likely crash the application + @throw [NSException exceptionWithName:@"CommandReturnError" reason:@"Failed to return command output" userInfo:nil]; + } +} + +/// Converts an NSString to an ObjCString struct +struct ObjCString nsStringToObjCString(NSString* string) { + size_t size = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + char *value = malloc(size); + [string getCString:value maxLength:size encoding:NSUTF8StringEncoding]; + + struct ObjCString objCString; + objCString.value = value; + objCString.size = size; + + return objCString; +} + +/// Converts a C-string to an NSString +NSString* cStringToNSString(char* string) { + return [[NSString alloc] initWithUTF8String:string]; +} + diff --git a/apps/desktop/desktop_native/objc/src/native/run_command.m b/apps/desktop/desktop_native/objc/src/native/run_command.m new file mode 100644 index 00000000000..eb90a3340db --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/run_command.m @@ -0,0 +1,39 @@ +#import +#import "autofill/run_autofill_command.h" +#import "interop.h" +#import "utils.h" + +void pickAndRunCommand(void* context, NSDictionary *input) { + NSString *namespace = input[@"namespace"]; + + if ([namespace isEqual:@"autofill"]) { + return runAutofillCommand(context, input); + } + + _return(context, _error([NSString stringWithFormat:@"Unknown namespace: %@", namespace])); +} + +/// [Callable from Rust] +/// Runs a command with the given input JSON +/// This function is called from Rust and is the entry point for running Objective-C code. +/// It takes a JSON string as input, deserializes it, runs the command, and serializes the output. +/// It also catches any exceptions that occur during the command execution. +void runCommand(void *context, char* inputJson) { + @autoreleasepool { + @try { + NSString *inputString = cStringToNSString(inputJson); + + NSError *error = nil; + NSDictionary *input = parseJson(inputString, error); + if (error) { + NSLog(@"Error occured while deserializing input params: %@", error); + return _return(context, _error([NSString stringWithFormat:@"Error occured while deserializing input params: %@", error])); + } + + pickAndRunCommand(context, input); + } @catch (NSException *e) { + NSLog(@"Error occurred while running Objective-C command: %@", e); + _return(context, _error([NSString stringWithFormat:@"Error occurred while running Objective-C command: %@", e])); + } + } +} diff --git a/apps/desktop/desktop_native/objc/src/native/utils.h b/apps/desktop/desktop_native/objc/src/native/utils.h new file mode 100644 index 00000000000..50fc991d15f --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/utils.h @@ -0,0 +1,11 @@ +#ifndef UTILS_H +#define UTILS_H + +#import + +NSDictionary *parseJson(NSString *jsonString, NSError *error); +NSString *serializeJson(NSDictionary *dictionary, NSError *error); + +NSData *decodeBase64URL(NSString *base64URLString); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/utils.m b/apps/desktop/desktop_native/objc/src/native/utils.m new file mode 100644 index 00000000000..040c723a8ac --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/utils.m @@ -0,0 +1,28 @@ +#import "utils.h" + +NSDictionary *parseJson(NSString *jsonString, NSError *error) { + NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if (error) { + return nil; + } + return json; +} + +NSString *serializeJson(NSDictionary *dictionary, NSError *error) { + NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error]; + if (error) { + return nil; + } + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} + +NSData *decodeBase64URL(NSString *base64URLString) { + NSString *base64String = [base64URLString stringByReplacingOccurrencesOfString:@"-" withString:@"+"]; + base64String = [base64String stringByReplacingOccurrencesOfString:@"_" withString:@"/"]; + + NSData *nsdataFromBase64String = [[NSData alloc] + initWithBase64EncodedString:base64String options:0]; + + return nsdataFromBase64String; +} diff --git a/apps/desktop/desktop_native/proxy/Cargo.toml b/apps/desktop/desktop_native/proxy/Cargo.toml index a1cefca7a3f..3618a11a921 100644 --- a/apps/desktop/desktop_native/proxy/Cargo.toml +++ b/apps/desktop/desktop_native/proxy/Cargo.toml @@ -7,13 +7,13 @@ version = "0.0.0" publish = false [dependencies] -anyhow = "=1.0.93" -desktop_core = { path = "../core", default-features = false } -futures = "0.3.30" -log = "0.4.22" -simplelog = "0.12.2" -tokio = { version = "1.38.0", features = ["io-std", "io-util", "macros", "rt"] } -tokio-util = { version = "0.7.11", features = ["codec"] } +anyhow = "=1.0.94" +desktop_core = { path = "../core" } +futures = "=0.3.31" +log = "=0.4.22" +simplelog = "=0.12.2" +tokio = { version = "=1.41.1", features = ["io-std", "io-util", "macros", "rt"] } +tokio-util = { version = "=0.7.12", features = ["codec"] } [target.'cfg(target_os = "macos")'.dependencies] -embed_plist = "1.2.2" +embed_plist = "=1.2.2" diff --git a/apps/desktop/desktop_native/proxy/src/main.rs b/apps/desktop/desktop_native/proxy/src/main.rs index 7d3b4ecfca7..ba29e00cf13 100644 --- a/apps/desktop/desktop_native/proxy/src/main.rs +++ b/apps/desktop/desktop_native/proxy/src/main.rs @@ -5,17 +5,20 @@ use futures::{FutureExt, SinkExt, StreamExt}; use log::*; use tokio_util::codec::LengthDelimitedCodec; +#[cfg(target_os = "windows")] +mod windows; + #[cfg(target_os = "macos")] embed_plist::embed_info_plist!("../../../resources/info.desktop_proxy.plist"); -fn init_logging(log_path: &Path, level: log::LevelFilter) { +fn init_logging(log_path: &Path, console_level: LevelFilter, file_level: LevelFilter) { use simplelog::{ColorChoice, CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode}; let config = Config::default(); let mut loggers: Vec> = Vec::new(); loggers.push(TermLogger::new( - level, + console_level, config.clone(), TerminalMode::Stderr, ColorChoice::Auto, @@ -23,7 +26,7 @@ fn init_logging(log_path: &Path, level: log::LevelFilter) { match std::fs::File::create(log_path) { Ok(file) => { - loggers.push(simplelog::WriteLogger::new(level, config, file)); + loggers.push(simplelog::WriteLogger::new(file_level, config, file)); } Err(e) => { eprintln!("Can't create file: {}", e); @@ -49,6 +52,9 @@ fn init_logging(log_path: &Path, level: log::LevelFilter) { /// #[tokio::main(flavor = "current_thread")] async fn main() { + #[cfg(target_os = "windows")] + let should_foreground = windows::allow_foreground(); + let sock_path = desktop_core::ipc::path("bitwarden"); let log_path = { @@ -57,7 +63,12 @@ async fn main() { path }; - init_logging(&log_path, LevelFilter::Info); + let level = std::env::var("PROXY_LOG_LEVEL") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(LevelFilter::Info); + + init_logging(&log_path, level, LevelFilter::Info); info!("Starting Bitwarden IPC Proxy."); @@ -137,6 +148,9 @@ async fn main() { // Listen to stdin and send messages to ipc processor. msg = stdin.next() => { + #[cfg(target_os = "windows")] + should_foreground.store(true, std::sync::atomic::Ordering::Relaxed); + match msg { Some(Ok(msg)) => { let m = String::from_utf8(msg.to_vec()).unwrap(); diff --git a/apps/desktop/desktop_native/proxy/src/windows.rs b/apps/desktop/desktop_native/proxy/src/windows.rs new file mode 100644 index 00000000000..cb0656fc7f8 --- /dev/null +++ b/apps/desktop/desktop_native/proxy/src/windows.rs @@ -0,0 +1,23 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +pub fn allow_foreground() -> Arc { + let should_foreground = Arc::new(AtomicBool::new(false)); + let should_foreground_clone = should_foreground.clone(); + let _ = std::thread::spawn(move || loop { + if !should_foreground_clone.load(Ordering::Relaxed) { + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + should_foreground_clone.store(false, Ordering::Relaxed); + + for _ in 0..60 { + desktop_core::biometric::windows_focus::focus_security_prompt(); + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + }); + + should_foreground +} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 53c20b7faf0..4302f302473 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -18,14 +18,9 @@ "**/*", "!**/node_modules/@bitwarden/desktop-napi/**/*", "**/node_modules/@bitwarden/desktop-napi/index.js", - "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node", - - "!**/node_modules/argon2/**/*", - "**/node_modules/argon2/argon2.cjs", - "**/node_modules/argon2/package.json", - "**/node_modules/argon2/build/Release/argon2.node" + "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], - "electronVersion": "32.1.1", + "electronVersion": "33.3.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", @@ -138,7 +133,7 @@ "entitlements": "resources/entitlements.mas.plist", "entitlementsInherit": "resources/entitlements.mas.inherit.plist", "entitlementsLoginHelper": "resources/entitlements.mas.loginhelper.plist", - "hardenedRuntime": false, + "hardenedRuntime": true, "extendInfo": { "LSMinimumSystemVersion": "12", "ElectronTeamID": "LTZ2PFU5D6" @@ -162,7 +157,7 @@ "applicationId": "bitwardendesktop", "identityName": "8bitSolutionsLLC.bitwardendesktop", "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", - "publisherDisplayName": "8bit Solutions LLC", + "publisherDisplayName": "Bitwarden Inc", "languages": [ "en-US", "af", @@ -229,7 +224,7 @@ }, "deb": { "artifactName": "${productName}-${version}-${arch}.${ext}", - "depends": ["libnotify4", "libxtst6", "libnss3", "libsecret-1-0", "libxss1"] + "depends": ["libnotify4", "libxtst6", "libnss3", "libxss1"] }, "appImage": { "artifactName": "${productName}-${version}-${arch}.${ext}" @@ -246,7 +241,16 @@ "autoStart": true, "base": "core22", "confinement": "strict", - "plugs": ["default", "network-bind", "password-manager-service"], + "plugs": [ + "default", + "network-bind", + "password-manager-service", + { + "polkit": { + "action-prefix": "com.bitwarden.Bitwarden" + } + } + ], "stagePackages": ["default"] }, "protocols": [ diff --git a/apps/desktop/macos/.gitignore b/apps/desktop/macos/.gitignore new file mode 100644 index 00000000000..e5d4324b213 --- /dev/null +++ b/apps/desktop/macos/.gitignore @@ -0,0 +1 @@ +BitwardenMacosProvider.swift diff --git a/apps/desktop/macos/README.md b/apps/desktop/macos/README.md new file mode 100644 index 00000000000..6e016144b4b --- /dev/null +++ b/apps/desktop/macos/README.md @@ -0,0 +1,23 @@ +# MacOS Extensions for Desktop Apps + +This folder contains an Xcode project that builds macOS extensions for our desktop app. The extensions are used to provide additional functionality to the desktop app, such as autofill (password and passkeys). + +## Manage loaded extensions + +macOS automatically loads extensions from apps, even if they have never been used (especially if built with Xcode). This can be confusing when you have multiple copies of the same application. To see where an extension is loaded from, use the following command: + +```bash +# To list all extensions +pluginkit -m -v + +# To list a specific extension +pluginkit -m -v -i com.bitwarden.desktop.autofill-extension +``` + +To unregister an extension, you can either remove it from your filesystem, or use the following command: + +```bash +pluginkit -r +``` + +where the path to the .appex file can be found in the output of the first command. diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib new file mode 100644 index 00000000000..ace3497a58b --- /dev/null +++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift new file mode 100644 index 00000000000..dbaa8517086 --- /dev/null +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -0,0 +1,226 @@ +// +// CredentialProviderViewController.swift +// autofill-extension +// +// Created by Andreas Coroiu on 2023-12-21. +// + +import AuthenticationServices +import os + +class CredentialProviderViewController: ASCredentialProviderViewController { + let logger: Logger + + // There is something a bit strange about the initialization/deinitialization in this class. + // Sometimes deinit won't be called after a request has successfully finished, + // which would leave this class hanging in memory and the IPC connection open. + // + // If instead I make this a static, the deinit gets called correctly after each request. + // I think we still might want a static regardless, to be able to reuse the connection if possible. + static let client: MacOsProviderClient = { + let instance = MacOsProviderClient.connect() + // setup code + return instance + }() + + init() { + logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider") + + logger.log("[autofill-extension] initializing extension") + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + logger.log("[autofill-extension] deinitializing extension") + } + + + @IBAction func cancel(_ sender: AnyObject?) { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) + } + + @IBAction func passwordSelected(_ sender: AnyObject?) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } + + /* + Implement this method if your extension supports showing credentials in the QuickType bar. + When the user selects a credential from your app, this method will be called with the + ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. + Provide the password by completing the extension request with the associated ASPasswordCredential. + If using the credential would require showing custom UI for authenticating the user, cancel + the request with error code ASExtensionError.userInteractionRequired. + + */ + + // Deprecated + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + logger.log("[autofill-extension] provideCredentialWithoutUserInteraction called \(credentialIdentity)") + logger.log("[autofill-extension] user \(credentialIdentity.user)") + logger.log("[autofill-extension] id \(credentialIdentity.recordIdentifier ?? "")") + logger.log("[autofill-extension] sid \(credentialIdentity.serviceIdentifier.identifier)") + logger.log("[autofill-extension] sidt \(credentialIdentity.serviceIdentifier.type.rawValue)") + +// let databaseIsUnlocked = true +// if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: credentialIdentity.user, password: "example1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) +// } else { +// self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) +// } + } + + override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) { + if let request = credentialRequest as? ASPasskeyCredentialRequest { + if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity { + + logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(passkey) called \(request)") + + class CallbackImpl: PreparePasskeyAssertionCallback { + let ctx: ASCredentialProviderExtensionContext + required init(_ ctx: ASCredentialProviderExtensionContext) { + self.ctx = ctx + } + + func onComplete(credential: PasskeyAssertionResponse) { + ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential( + userHandle: credential.userHandle, + relyingParty: credential.rpId, + signature: credential.signature, + clientDataHash: credential.clientDataHash, + authenticatorData: credential.authenticatorData, + credentialID: credential.credentialId + )) + } + + func onError(error: BitwardenError) { + ctx.cancelRequest(withError: error) + } + } + + let userVerification = switch request.userVerificationPreference { + case .preferred: + UserVerification.preferred + case .required: + UserVerification.required + default: + UserVerification.discouraged + } + + let req = PasskeyAssertionRequest( + rpId: passkeyIdentity.relyingPartyIdentifier, + credentialId: passkeyIdentity.credentialID, + userName: passkeyIdentity.userName, + userHandle: passkeyIdentity.userHandle, + recordIdentifier: passkeyIdentity.recordIdentifier, + clientDataHash: request.clientDataHash, + userVerification: userVerification + ) + + CredentialProviderViewController.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext)) + return + } + } + + if let request = credentialRequest as? ASPasswordCredentialRequest { + logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(password) called \(request)") + return; + } + + logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2 called wrong") + self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid authentication request")) + } + + /* + Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with + ASExtensionError.userInteractionRequired. In this case, the system may present your extension's + UI and call this method. Show appropriate UI for authenticating the user then provide the password + by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ + + + override func prepareInterfaceForExtensionConfiguration() { + logger.log("[autofill-extension] prepareInterfaceForExtensionConfiguration called") + } + + override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) { + if let request = registrationRequest as? ASPasskeyCredentialRequest { + if let passkeyIdentity = registrationRequest.credentialIdentity as? ASPasskeyCredentialIdentity { + class CallbackImpl: PreparePasskeyRegistrationCallback { + let ctx: ASCredentialProviderExtensionContext + required init(_ ctx: ASCredentialProviderExtensionContext) { + self.ctx = ctx + } + + func onComplete(credential: PasskeyRegistrationResponse) { + ctx.completeRegistrationRequest(using: ASPasskeyRegistrationCredential( + relyingParty: credential.rpId, + clientDataHash: credential.clientDataHash, + credentialID: credential.credentialId, + attestationObject: credential.attestationObject + )) + } + + func onError(error: BitwardenError) { + ctx.cancelRequest(withError: error) + } + } + + let userVerification = switch request.userVerificationPreference { + case .preferred: + UserVerification.preferred + case .required: + UserVerification.required + default: + UserVerification.discouraged + } + + let req = PasskeyRegistrationRequest( + rpId: passkeyIdentity.relyingPartyIdentifier, + userName: passkeyIdentity.userName, + userHandle: passkeyIdentity.userHandle, + clientDataHash: request.clientDataHash, + userVerification: userVerification, + supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) } + ) + CredentialProviderViewController.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext)) + return + } + } + + // If we didn't get a passkey, return an error + self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid registration request")) + } + + /* + Prepare your UI to list available credentials for the user to choose from. The items in + 'serviceIdentifiers' describe the service the user is logging in to, so your extension can + prioritize the most relevant credentials in the list. + */ + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { + logger.log("[autofill-extension] prepareCredentialList for serviceIdentifiers: \(serviceIdentifiers.count)") + + for serviceIdentifier in serviceIdentifiers { + logger.log(" service: \(serviceIdentifier.identifier)") + } + } + + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier], requestParameters: ASPasskeyCredentialRequestParameters) { + logger.log("[autofill-extension] prepareCredentialList(passkey) for serviceIdentifiers: \(serviceIdentifiers.count)") + logger.log("request parameters: \(requestParameters.relyingPartyIdentifier)") + + for serviceIdentifier in serviceIdentifiers { + logger.log(" service: \(serviceIdentifier.identifier)") + } + } + +} diff --git a/apps/desktop/macos/autofill-extension/Info.plist b/apps/desktop/macos/autofill-extension/Info.plist new file mode 100644 index 00000000000..539cfa35b9d --- /dev/null +++ b/apps/desktop/macos/autofill-extension/Info.plist @@ -0,0 +1,23 @@ + + + + + NSExtension + + NSExtensionAttributes + + ASCredentialProviderExtensionCapabilities + + ProvidesPasskeys + + + ASCredentialProviderExtensionShowsConfigurationUI + + + NSExtensionPointIdentifier + com.apple.authentication-services-credential-provider-ui + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).CredentialProviderViewController + + + diff --git a/apps/desktop/macos/autofill-extension/autofill_extension.entitlements b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements new file mode 100644 index 00000000000..86c7195768e --- /dev/null +++ b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + LTZ2PFU5D6.com.bitwarden.desktop + + + diff --git a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..2ac467f3289 --- /dev/null +++ b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj @@ -0,0 +1,375 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 3368DB392C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3368DB382C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework */; }; + 3368DB3B2C654F3800896B75 /* BitwardenMacosProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3368DB3A2C654F3800896B75 /* BitwardenMacosProvider.swift */; }; + E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */; }; + E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */; }; + E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */; }; +/* End PBXBuildFile section */ + +/* 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 = ""; }; + 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 = ""; }; + E1DF71442B342F6900F29026 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CredentialProviderViewController.xib; sourceTree = ""; }; + E1DF71462B342F6900F29026 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E1DF71472B342F6900F29026 /* autofill_extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = autofill_extension.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E1DF71392B342F6900F29026 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */, + 3368DB392C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E1DF711D2B342E2800F29026 = { + isa = PBXGroup; + children = ( + 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */, + E1DF71402B342F6900F29026 /* autofill-extension */, + E1DF713D2B342F6900F29026 /* Frameworks */, + E1DF71272B342E2800F29026 /* Products */, + ); + sourceTree = ""; + }; + E1DF71272B342E2800F29026 /* Products */ = { + isa = PBXGroup; + children = ( + E1DF713C2B342F6900F29026 /* autofill-extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + E1DF713D2B342F6900F29026 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */, + 3368DB382C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E1DF71402B342F6900F29026 /* autofill-extension */ = { + isa = PBXGroup; + children = ( + 3368DB3A2C654F3800896B75 /* BitwardenMacosProvider.swift */, + E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */, + E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */, + E1DF71462B342F6900F29026 /* Info.plist */, + E1DF71472B342F6900F29026 /* autofill_extension.entitlements */, + ); + path = "autofill-extension"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E1DF713B2B342F6900F29026 /* autofill-extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */; + buildPhases = ( + E1DF71382B342F6900F29026 /* Sources */, + E1DF71392B342F6900F29026 /* Frameworks */, + E1DF713A2B342F6900F29026 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "autofill-extension"; + productName = "autofill-extension"; + productReference = E1DF713C2B342F6900F29026 /* autofill-extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E1DF711E2B342E2800F29026 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1510; + LastUpgradeCheck = 1510; + TargetAttributes = { + E1DF713B2B342F6900F29026 = { + CreatedOnToolsVersion = 15.1; + }; + }; + }; + buildConfigurationList = E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E1DF711D2B342E2800F29026; + productRefGroup = E1DF71272B342E2800F29026 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E1DF713B2B342F6900F29026 /* autofill-extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E1DF713A2B342F6900F29026 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E1DF71382B342F6900F29026 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3368DB3B2C654F3800896B75 /* BitwardenMacosProvider.swift in Sources */, + E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + E1DF71442B342F6900F29026 /* Base */, + ); + name = CredentialProviderViewController.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E1DF71332B342E2900F29026 /* Debug */ = { + isa = XCBuildConfiguration; + 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; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E1DF71342B342E2900F29026 /* Release */ = { + isa = XCBuildConfiguration; + 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 = Release; + }; + E1DF714C2B342F6900F29026 /* Debug */ = { + isa = XCBuildConfiguration; + 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 = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + E1DF714D2B342F6900F29026 /* Release */ = { + isa = XCBuildConfiguration; + 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 = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1DF71332B342E2900F29026 /* Debug */, + E1DF71342B342E2900F29026 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1DF714C2B342F6900F29026 /* Debug */, + E1DF714D2B342F6900F29026 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E1DF711E2B342E2800F29026 /* Project object */; +} diff --git a/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/desktop/macos/production.xcconfig b/apps/desktop/macos/production.xcconfig new file mode 100644 index 00000000000..f06f2bf736e --- /dev/null +++ b/apps/desktop/macos/production.xcconfig @@ -0,0 +1,11 @@ +// +// 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 f57f067907a..f727c903a7f 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -14,11 +14,11 @@ "module-alias": "2.2.3", "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.17.1", + "@types/node": "22.10.5", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,12 +106,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz", - "integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/node-ipc": { @@ -415,15 +415,15 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "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 ed2c4bb29cf..b7da729d7d1 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -19,11 +19,11 @@ "module-alias": "2.2.3", "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.17.1", + "@types/node": "22.10.5", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts index 59313bf6cb1..8d2d734677a 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "module-alias/register"; import yargs from "yargs"; @@ -5,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts index b71b984dc95..93598bf9eef 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import "module-alias/register"; import yargs from "yargs"; @@ -5,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/deferred.ts b/apps/desktop/native-messaging-test-runner/src/deferred.ts index b6478bcf268..da34d80ebb2 100644 --- a/apps/desktop/native-messaging-test-runner/src/deferred.ts +++ b/apps/desktop/native-messaging-test-runner/src/deferred.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore // Wrapper for a promise that we can await the promise in one case // while allowing an unrelated event to fulfill it elsewhere. export default class Deferred { 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 be79f6939a8..8513363956e 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -1,8 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { homedir } from "os"; import * as NodeIPC from "node-ipc"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import Deferred from "./deferred"; 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 cd84504c630..94fdde026b2 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 @@ -9,13 +9,21 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; +// eslint-disable-next-line no-restricted-imports import { DecryptedCommandData } from "../../src/models/native-messaging/decrypted-command-data"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessage } from "../../src/models/native-messaging/encrypted-message"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessageResponse } from "../../src/models/native-messaging/encrypted-message-response"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessage } from "../../src/models/native-messaging/unencrypted-message"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import IPCService, { IPCOptions } from "./ipc.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/race.ts b/apps/desktop/native-messaging-test-runner/src/race.ts index 7aba3aa41f9..5ed778aa35b 100644 --- a/apps/desktop/native-messaging-test-runner/src/race.ts +++ b/apps/desktop/native-messaging-test-runner/src/race.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export const race = ({ promise, timeout, diff --git a/apps/desktop/native-messaging-test-runner/tsconfig.json b/apps/desktop/native-messaging-test-runner/tsconfig.json index a34554a264f..72a28de3f7a 100644 --- a/apps/desktop/native-messaging-test-runner/tsconfig.json +++ b/apps/desktop/native-messaging-test-runner/tsconfig.json @@ -11,7 +11,12 @@ "@src/*": ["src/*"], "@bitwarden/node/*": ["../../../libs/node/src/*"], "@bitwarden/common/*": ["../../../libs/common/src/*"] - } + }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ] }, "exclude": ["node_modules"] } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c9e33b7110a..8f6c6525a39 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": "2024.11.0", + "version": "2025.1.2", "keywords": [ "bitwarden", "password", @@ -19,10 +19,11 @@ "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", "build-native": "cd desktop_native && node build.js", - "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", + "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "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: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", @@ -33,11 +34,13 @@ "electron:ignore": "node ./scripts/start.js --ignore-certificate-errors", "clean:dist": "rimraf ./dist", "pack:dir": "npm run clean:dist && electron-builder --dir -p never", - "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never", + "pack:lin:flatpak": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --repo=build/.repo build/.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ./build/.repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", + "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 && mksquashfs ./dist/tmp-snap/ $SNAP_FILE -noappend -comp lzo -no-fragments && rm -rf ./dist/tmp-snap/", "pack:mac": "npm run clean:dist && 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: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:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", "dist:dir": "npm run build && npm run pack:dir", @@ -45,6 +48,7 @@ "dist:mac": "npm run build && npm run pack:mac", "dist:mac:mas": "npm run build && npm run pack:mac:mas", "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", "dist:win:ci": "npm run build && npm run pack:win:ci", "publish:lin": "npm run build && npm run clean:dist && electron-builder --linux --x64 -p always", diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/desktop/postcss.config.js +++ b/apps/desktop/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml new file mode 100644 index 00000000000..fea28052f8d --- /dev/null +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -0,0 +1,45 @@ +app-id: com.bitwarden.desktop +runtime: org.freedesktop.Platform +runtime-version: "24.08" +sdk: org.freedesktop.Sdk +base: org.electronjs.Electron2.BaseApp +base-version: "24.08" +command: bitwarden.sh +finish-args: + - --share=ipc + - --share=network + - --socket=x11 + - --device=dri + - --env=XDG_CURRENT_DESKTOP=Unity + - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons + - --talk-name=org.kde.StatusNotifierWatcher + - --talk-name=org.freedesktop.Notifications + - --talk-name=org.freedesktop.secrets + - --talk-name=com.canonical.AppMenu.Registrar + - --system-talk-name=org.freedesktop.PolicyKit1 + # Lock on lockscreen + - --talk-name=org.gnome.ScreenSaver + - --talk-name=org.freedesktop.ScreenSaver + - --system-talk-name=org.freedesktop.login1 + - --filesystem=xdg-download +modules: + - name: bitwarden-desktop + buildsystem: simple + build-commands: + - mkdir -p /app/bin + - mkdir -p /app/bin/Bitwarden/ + - cp -r ./* /app/bin/ + - install bitwarden.sh /app/bin/bitwarden.sh + sources: + - type: dir + only-arches: [x86_64] + path: ../dist/linux-unpacked + - type: dir + only-arches: [aarch64] + path: ../dist/linux-arm64-unpacked + - type: script + dest-filename: bitwarden.sh + commands: + - ulimit -c 0 + - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" + - exec zypak-wrapper /app/bin/bitwarden-app "$@" diff --git a/apps/desktop/resources/com.bitwarden.desktop.policy b/apps/desktop/resources/com.bitwarden.desktop.policy new file mode 100644 index 00000000000..e48bc6b8fbb --- /dev/null +++ b/apps/desktop/resources/com.bitwarden.desktop.policy @@ -0,0 +1,16 @@ + + + + + + Unlock Bitwarden + Authenticate to unlock Bitwarden + + no + no + auth_self + + + diff --git a/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist b/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist index 794eada1cad..fca5f02d52d 100644 --- a/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist +++ b/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist @@ -6,5 +6,7 @@ com.apple.security.inherit + com.apple.security.cs.allow-jit + diff --git a/apps/desktop/resources/entitlements.desktop_proxy.plist b/apps/desktop/resources/entitlements.desktop_proxy.plist index d5c7b8a2cc8..1a39a482389 100644 --- a/apps/desktop/resources/entitlements.desktop_proxy.plist +++ b/apps/desktop/resources/entitlements.desktop_proxy.plist @@ -8,5 +8,7 @@ LTZ2PFU5D6.com.bitwarden.desktop + com.apple.security.cs.allow-jit + diff --git a/apps/desktop/resources/entitlements.mac.plist b/apps/desktop/resources/entitlements.mac.plist index 48f7bf5cece..e273bcc7eca 100644 --- a/apps/desktop/resources/entitlements.mac.plist +++ b/apps/desktop/resources/entitlements.mac.plist @@ -4,9 +4,9 @@ com.apple.security.cs.allow-jit - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-library-validation + diff --git a/apps/desktop/resources/entitlements.mas.inherit.plist b/apps/desktop/resources/entitlements.mas.inherit.plist index 3ee76423e4c..7e957fce7ce 100644 --- a/apps/desktop/resources/entitlements.mas.inherit.plist +++ b/apps/desktop/resources/entitlements.mas.inherit.plist @@ -6,9 +6,11 @@ com.apple.security.inherit - com.apple.security.cs.allow-unsigned-executable-memory + com.apple.security.cs.allow-jit - com.apple.security.cs.disable-library-validation + diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index d42ade962c3..0450111bebd 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -16,6 +16,10 @@ com.apple.security.files.user-selected.read-write + com.apple.security.temporary-exception.files.home-relative-path.read-write /Library/Application Support/Mozilla/NativeMessagingHosts/ @@ -30,5 +34,7 @@ /Library/Application Support/Microsoft Edge Canary/NativeMessagingHosts/ /Library/Application Support/Vivaldi/NativeMessagingHosts/ + com.apple.security.cs.allow-jit + diff --git a/apps/desktop/resources/memory-dump-wrapper.sh b/apps/desktop/resources/memory-dump-wrapper.sh index b62c050683a..6737cc312f4 100644 --- a/apps/desktop/resources/memory-dump-wrapper.sh +++ b/apps/desktop/resources/memory-dump-wrapper.sh @@ -7,6 +7,12 @@ ulimit -c 0 RAW_PATH=$(readlink -f "$0") APP_PATH=$(dirname $RAW_PATH) +# force use of base image libdus in snap +if [ -e "/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" ] +then + export LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" +fi + # pass through all args $APP_PATH/bitwarden-app "$@" diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index fd16cd5ffbe..30b17a44d12 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const child_process = require("child_process"); const path = require("path"); diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index 69c078a13b5..20c24c8a76b 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const path = require("path"); @@ -15,36 +15,62 @@ async function run(context) { const appName = context.packager.appInfo.productFilename; const appPath = `${context.appOutDir}/${appName}.app`; const macBuild = context.electronPlatformName === "darwin"; - const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName); + const copySafariExtension = ["darwin", "mas"].includes(context.electronPlatformName); + const copyAutofillExtension = ["mas"].includes(context.electronPlatformName); - if (copyPlugIn) { + let shouldResign = false; + + // cannot use extraFiles because it modifies the extensions .plist and makes it invalid + if (copyAutofillExtension) { + console.log("### Copying autofill extension"); + const extensionPath = path.join(__dirname, "../macos/dist/autofill-extension.appex"); + if (!fse.existsSync(extensionPath)) { + console.log("### Autofill extension not found - skipping"); + } else { + if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) { + fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + } + fse.copySync(extensionPath, path.join(appPath, "Contents/PlugIns/autofill-extension.appex")); + shouldResign = true; + } + } + + if (copySafariExtension) { + console.log("### Copying safari extension"); // Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552 const plugIn = path.join(__dirname, "../PlugIns"); - if (fse.existsSync(plugIn)) { - fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + if (!fse.existsSync(plugIn)) { + console.log("### Safari extension not found - skipping"); + } else { + if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) { + fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + } fse.copySync( path.join(plugIn, "safari.appex"), path.join(appPath, "Contents/PlugIns/safari.appex"), ); + shouldResign = true; + } + } - // Resign to sign safari extension - if (context.electronPlatformName === "mas") { - const masBuildOptions = deepAssign( - {}, - context.packager.platformSpecificBuildOptions, - context.packager.config.mas, - ); - if (context.targets.some((e) => e.name === "mas-dev")) { - deepAssign(masBuildOptions, { - type: "development", - }); - } - if (context.packager.packagerOptions.prepackaged == null) { - await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); - } - } else { - await context.packager.signApp(context, true); + if (shouldResign) { + // Resign to sign safari extension + if (context.electronPlatformName === "mas") { + const masBuildOptions = deepAssign( + {}, + context.packager.platformSpecificBuildOptions, + context.packager.config.mas, + ); + if (context.targets.some((e) => e.name === "mas-dev")) { + deepAssign(masBuildOptions, { + type: "development", + }); + } + if (context.packager.packagerOptions.prepackaged == null) { + await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); } + } else { + await context.packager.signApp(context, true); } } diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js new file mode 100644 index 00000000000..649fe3b6736 --- /dev/null +++ b/apps/desktop/scripts/build-macos-extension.js @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ +const child = require("child_process"); +const { exit } = require("process"); + +const fse = require("fs-extra"); + +const paths = { + macosBuild: "./macos/build", + extensionBuild: "./macos/build/Release/autofill-extension.appex", + extensionDistDir: "./macos/dist", + extensionDist: "./macos/dist/autofill-extension.appex", + macOsProject: "./macos/desktop.xcodeproj", + macOsConfig: "./macos/production.xcconfig", +}; + +async function buildMacOs() { + if (fse.existsSync(paths.macosBuild)) { + fse.removeSync(paths.macosBuild); + } + + if (fse.existsSync(paths.extensionDistDir)) { + fse.removeSync(paths.extensionDistDir); + } + + const proc = child.spawn("xcodebuild", [ + "-project", + paths.macOsProject, + "-alltargets", + "-configuration", + "Release", + // Uncomment when signing is fixed + // "-xcconfig", + // paths.macOsConfig, + ]); + stdOutProc(proc); + await new Promise((resolve, reject) => + proc.on("close", (code) => { + if (code > 0) { + console.error("xcodebuild failed with code", code); + return reject(new Error(`xcodebuild failed with code ${code}`)); + } + console.log("xcodebuild success"); + resolve(); + }), + ); + + fse.mkdirSync(paths.extensionDistDir); + fse.copySync(paths.extensionBuild, paths.extensionDist); + // Delete the build dir, otherwise MacOS will load the extension from there instead of the Bitwarden.app bundle + fse.removeSync(paths.macosBuild); +} + +function stdOutProc(proc) { + proc.stdout.on("data", (data) => console.log(data.toString())); + proc.stderr.on("data", (data) => console.error(data.toString())); +} + +buildMacOs() + .then(() => console.log("macOS build complete")) + .catch((err) => { + console.error("macOS build failed", err); + exit(-1); + }); diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index c1a2fb3cffc..d2c984a6f24 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const concurrently = require("concurrently"); const rimraf = require("rimraf"); diff --git a/apps/desktop/sign.js b/apps/desktop/sign.js index 74c63932306..f0110ea195b 100644 --- a/apps/desktop/sign.js +++ b/apps/desktop/sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ exports.default = async function (configuration) { if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") { diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 7336ce09dd8..709506f576f 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -334,7 +334,7 @@

{{ startToTrayDescText }}
-
+
-
-
- - -
-
- -
-
- - -
-
- - diff --git a/apps/desktop/src/auth/lock.component.spec.ts b/apps/desktop/src/auth/lock.component.spec.ts deleted file mode 100644 index b67a386845f..00000000000 --- a/apps/desktop/src/auth/lock.component.spec.ts +++ /dev/null @@ -1,476 +0,0 @@ -import { NO_ERRORS_SCHEMA } from "@angular/core"; -import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing"; -import { ActivatedRoute } from "@angular/router"; -import { MockProxy, mock } from "jest-mock-extended"; -import { of } from "rxjs"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.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 { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { 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 { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; -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 { DialogService, ToastService } from "@bitwarden/components"; -import { - KeyService, - BiometricsService as AbstractBiometricService, - BiometricStateService, -} from "@bitwarden/key-management"; - -import { BiometricsService } from "../key-management/biometrics/biometrics.service"; - -import { LockComponent } from "./lock.component"; - -// ipc mock global -const isWindowVisibleMock = jest.fn(); -(global as any).ipc = { - platform: { - isWindowVisible: isWindowVisibleMock, - }, - keyManagement: { - biometric: { - enabled: jest.fn(), - }, - }, -}; - -describe("LockComponent", () => { - let component: LockComponent; - let fixture: ComponentFixture; - let stateServiceMock: MockProxy; - let biometricStateService: MockProxy; - let biometricsService: MockProxy; - let messagingServiceMock: MockProxy; - let broadcasterServiceMock: MockProxy; - let platformUtilsServiceMock: MockProxy; - let activatedRouteMock: MockProxy; - let mockMasterPasswordService: FakeMasterPasswordService; - let mockToastService: MockProxy; - - const mockUserId = Utils.newGuid() as UserId; - const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); - - beforeEach(async () => { - stateServiceMock = mock(); - - messagingServiceMock = mock(); - broadcasterServiceMock = mock(); - platformUtilsServiceMock = mock(); - mockToastService = mock(); - - activatedRouteMock = mock(); - activatedRouteMock.queryParams = mock(); - - mockMasterPasswordService = new FakeMasterPasswordService(); - - biometricStateService = mock(); - biometricStateService.dismissedRequirePasswordOnStartCallout$ = of(false); - biometricStateService.promptAutomatically$ = of(false); - biometricStateService.promptCancelled$ = of(false); - - await TestBed.configureTestingModule({ - declarations: [LockComponent, I18nPipe], - providers: [ - { provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService }, - { - provide: I18nService, - useValue: mock(), - }, - { - provide: PlatformUtilsService, - useValue: platformUtilsServiceMock, - }, - { - provide: MessagingService, - useValue: messagingServiceMock, - }, - { - provide: KeyService, - useValue: mock(), - }, - { - provide: VaultTimeoutService, - useValue: mock(), - }, - { - provide: VaultTimeoutSettingsService, - useValue: mock(), - }, - { - provide: EnvironmentService, - useValue: mock(), - }, - { - provide: StateService, - useValue: stateServiceMock, - }, - { - provide: ApiService, - useValue: mock(), - }, - { - provide: ActivatedRoute, - useValue: activatedRouteMock, - }, - { - provide: BroadcasterService, - useValue: broadcasterServiceMock, - }, - { - provide: PolicyApiServiceAbstraction, - useValue: mock(), - }, - { - provide: InternalPolicyService, - useValue: mock(), - }, - { - provide: PasswordStrengthServiceAbstraction, - useValue: mock(), - }, - { - provide: LogService, - useValue: mock(), - }, - { - provide: DialogService, - useValue: mock(), - }, - { - provide: DeviceTrustServiceAbstraction, - useValue: mock(), - }, - { - provide: UserVerificationService, - useValue: mock(), - }, - { - provide: PinServiceAbstraction, - useValue: mock(), - }, - { - provide: BiometricStateService, - useValue: biometricStateService, - }, - { - provide: AbstractBiometricService, - useValue: biometricsService, - }, - { - provide: AccountService, - useValue: accountService, - }, - { - provide: AuthService, - useValue: mock(), - }, - { - provide: KdfConfigService, - useValue: mock(), - }, - { - provide: SyncService, - useValue: mock(), - }, - { - provide: ToastService, - useValue: mockToastService, - }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(LockComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("ngOnInit", () => { - it("should call super.ngOnInit() once", async () => { - const superNgOnInitSpy = jest.spyOn(BaseLockComponent.prototype, "ngOnInit"); - await component.ngOnInit(); - expect(superNgOnInitSpy).toHaveBeenCalledTimes(1); - }); - - it('should set "autoPromptBiometric" to true if "biometricState.promptAutomatically$" resolves to true', async () => { - biometricStateService.promptAutomatically$ = of(true); - - await component.ngOnInit(); - expect(component["autoPromptBiometric"]).toBe(true); - }); - - it('should set "autoPromptBiometric" to false if "biometricState.promptAutomatically$" resolves to false', async () => { - biometricStateService.promptAutomatically$ = of(false); - - await component.ngOnInit(); - expect(component["autoPromptBiometric"]).toBe(false); - }); - - it('should set "biometricReady" to true if "stateService.getBiometricReady()" resolves to true', async () => { - component["canUseBiometric"] = jest.fn().mockResolvedValue(true); - - await component.ngOnInit(); - expect(component["biometricReady"]).toBe(true); - }); - - it('should set "biometricReady" to false if "stateService.getBiometricReady()" resolves to false', async () => { - component["canUseBiometric"] = jest.fn().mockResolvedValue(false); - - await component.ngOnInit(); - expect(component["biometricReady"]).toBe(false); - }); - - it("should call displayBiometricUpdateWarning", async () => { - component["displayBiometricUpdateWarning"] = jest.fn(); - await component.ngOnInit(); - expect(component["displayBiometricUpdateWarning"]).toHaveBeenCalledTimes(1); - }); - - it("should call delayedAskForBiometric", async () => { - component["delayedAskForBiometric"] = jest.fn(); - await component.ngOnInit(); - expect(component["delayedAskForBiometric"]).toHaveBeenCalledTimes(1); - expect(component["delayedAskForBiometric"]).toHaveBeenCalledWith(500); - }); - - it("should call delayedAskForBiometric when queryParams change", async () => { - activatedRouteMock.queryParams = of({ promptBiometric: true }); - component["delayedAskForBiometric"] = jest.fn(); - await component.ngOnInit(); - - expect(component["delayedAskForBiometric"]).toHaveBeenCalledTimes(1); - expect(component["delayedAskForBiometric"]).toHaveBeenCalledWith(500); - }); - - it("should call messagingService.send", async () => { - await component.ngOnInit(); - expect(messagingServiceMock.send).toHaveBeenCalledWith("getWindowIsFocused"); - }); - - describe("broadcasterService.subscribe", () => { - it('should call onWindowHidden() when "broadcasterService.subscribe" is called with "windowHidden"', async () => { - component["onWindowHidden"] = jest.fn(); - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ command: "windowHidden" }); - expect(component["onWindowHidden"]).toHaveBeenCalledTimes(1); - }); - - it('should call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is true and deferFocus is false', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = null; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: true, - } as any); - expect(component["deferFocus"]).toBe(false); - expect(component["focusInput"]).toHaveBeenCalledTimes(1); - }); - - it('should not call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is true and deferFocus is true', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = null; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: false, - } as any); - expect(component["deferFocus"]).toBe(true); - expect(component["focusInput"]).toHaveBeenCalledTimes(0); - }); - - it('should call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is true and deferFocus is true', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = true; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: true, - } as any); - expect(component["deferFocus"]).toBe(false); - expect(component["focusInput"]).toHaveBeenCalledTimes(1); - }); - - it('should not call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is false and deferFocus is true', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = true; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: false, - } as any); - expect(component["deferFocus"]).toBe(true); - expect(component["focusInput"]).toHaveBeenCalledTimes(0); - }); - }); - }); - - describe("ngOnDestroy", () => { - it("should call super.ngOnDestroy()", () => { - const superNgOnDestroySpy = jest.spyOn(BaseLockComponent.prototype, "ngOnDestroy"); - component.ngOnDestroy(); - expect(superNgOnDestroySpy).toHaveBeenCalledTimes(1); - }); - - it("should call broadcasterService.unsubscribe()", () => { - component.ngOnDestroy(); - expect(broadcasterServiceMock.unsubscribe).toHaveBeenCalledTimes(1); - }); - }); - - describe("focusInput", () => { - it('should call "focus" on #pin input if pinEnabled is true', () => { - component["pinEnabled"] = true; - global.document.getElementById = jest.fn().mockReturnValue({ focus: jest.fn() }); - component["focusInput"](); - expect(global.document.getElementById).toHaveBeenCalledWith("pin"); - }); - - it('should call "focus" on #masterPassword input if pinEnabled is false', () => { - component["pinEnabled"] = false; - global.document.getElementById = jest.fn().mockReturnValue({ focus: jest.fn() }); - component["focusInput"](); - expect(global.document.getElementById).toHaveBeenCalledWith("masterPassword"); - }); - }); - - describe("delayedAskForBiometric", () => { - beforeEach(() => { - component["supportsBiometric"] = true; - component["autoPromptBiometric"] = true; - }); - - it('should wait for "delay" milliseconds', fakeAsync(async () => { - const delaySpy = jest.spyOn(global, "setTimeout"); - // 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 - component["delayedAskForBiometric"](5000); - - tick(4000); - component["biometricAsked"] = false; - - tick(1000); - component["biometricAsked"] = true; - - expect(delaySpy).toHaveBeenCalledWith(expect.any(Function), 5000); - })); - - it('should return; if "params" is defined and "params.promptBiometric" is false', fakeAsync(async () => { - // 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 - component["delayedAskForBiometric"](5000, { promptBiometric: false }); - tick(5000); - expect(component["biometricAsked"]).toBe(false); - })); - - it('should not return; if "params" is defined and "params.promptBiometric" is true', fakeAsync(async () => { - // 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 - component["delayedAskForBiometric"](5000, { promptBiometric: true }); - tick(5000); - expect(component["biometricAsked"]).toBe(true); - })); - - it('should not return; if "params" is undefined', fakeAsync(async () => { - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - expect(component["biometricAsked"]).toBe(true); - })); - - it('should return; if "supportsBiometric" is false', fakeAsync(async () => { - component["supportsBiometric"] = false; - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - expect(component["biometricAsked"]).toBe(false); - })); - - it('should return; if "autoPromptBiometric" is false', fakeAsync(async () => { - component["autoPromptBiometric"] = false; - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - expect(component["biometricAsked"]).toBe(false); - })); - - it("should call unlockBiometric() if biometricAsked is false and window is visible", fakeAsync(async () => { - isWindowVisibleMock.mockResolvedValue(true); - component["unlockBiometric"] = jest.fn(); - component["biometricAsked"] = false; - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - - expect(component["unlockBiometric"]).toHaveBeenCalledTimes(1); - })); - - it("should not call unlockBiometric() if biometricAsked is false and window is not visible", fakeAsync(async () => { - isWindowVisibleMock.mockResolvedValue(false); - component["unlockBiometric"] = jest.fn(); - component["biometricAsked"] = false; - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - - expect(component["unlockBiometric"]).toHaveBeenCalledTimes(0); - })); - - it("should not call unlockBiometric() if biometricAsked is true", fakeAsync(async () => { - isWindowVisibleMock.mockResolvedValue(true); - component["unlockBiometric"] = jest.fn(); - component["biometricAsked"] = true; - - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - - expect(component["unlockBiometric"]).toHaveBeenCalledTimes(0); - })); - }); - - describe("canUseBiometric", () => { - it("should call biometric.enabled with current active user", async () => { - await component["canUseBiometric"](); - - expect(ipc.keyManagement.biometric.enabled).toHaveBeenCalledWith(mockUserId); - }); - }); - - it('onWindowHidden() should set "showPassword" to false', () => { - component["showPassword"] = true; - component["onWindowHidden"](); - expect(component["showPassword"]).toBe(false); - }); -}); diff --git a/apps/desktop/src/auth/lock.component.ts b/apps/desktop/src/auth/lock.component.ts deleted file mode 100644 index cc062965f35..00000000000 --- a/apps/desktop/src/auth/lock.component.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, map, switchMap } from "rxjs"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.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 { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { DeviceType } from "@bitwarden/common/enums"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { 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 { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; - -const BroadcasterSubscriptionId = "LockComponent"; - -@Component({ - selector: "app-lock", - templateUrl: "lock.component.html", -}) -export class LockComponent extends BaseLockComponent implements OnInit, OnDestroy { - private deferFocus: boolean = null; - protected biometricReady = false; - private biometricAsked = false; - private autoPromptBiometric = false; - private timerId: any; - - constructor( - masterPasswordService: InternalMasterPasswordServiceAbstraction, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - messagingService: MessagingService, - keyService: KeyService, - vaultTimeoutService: VaultTimeoutService, - vaultTimeoutSettingsService: VaultTimeoutSettingsService, - environmentService: EnvironmentService, - protected override stateService: StateService, - apiService: ApiService, - private route: ActivatedRoute, - private broadcasterService: BroadcasterService, - ngZone: NgZone, - policyApiService: PolicyApiServiceAbstraction, - policyService: InternalPolicyService, - passwordStrengthService: PasswordStrengthServiceAbstraction, - logService: LogService, - dialogService: DialogService, - deviceTrustService: DeviceTrustServiceAbstraction, - userVerificationService: UserVerificationService, - pinService: PinServiceAbstraction, - biometricStateService: BiometricStateService, - biometricsService: BiometricsService, - accountService: AccountService, - authService: AuthService, - kdfConfigService: KdfConfigService, - syncService: SyncService, - toastService: ToastService, - ) { - super( - masterPasswordService, - router, - i18nService, - platformUtilsService, - messagingService, - keyService, - vaultTimeoutService, - vaultTimeoutSettingsService, - environmentService, - stateService, - apiService, - logService, - ngZone, - policyApiService, - policyService, - passwordStrengthService, - dialogService, - deviceTrustService, - userVerificationService, - pinService, - biometricStateService, - biometricsService, - accountService, - authService, - kdfConfigService, - syncService, - toastService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - this.autoPromptBiometric = await firstValueFrom( - this.biometricStateService.promptAutomatically$, - ); - this.biometricReady = await this.canUseBiometric(); - - await this.displayBiometricUpdateWarning(); - - // 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.delayedAskForBiometric(500); - this.route.queryParams.pipe(switchMap((params) => this.delayedAskForBiometric(500, params))); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - case "windowIsFocused": - if (this.deferFocus === null) { - this.deferFocus = !message.windowIsFocused; - if (!this.deferFocus) { - this.focusInput(); - } - } else if (this.deferFocus && message.windowIsFocused) { - this.focusInput(); - this.deferFocus = false; - } - break; - default: - } - }); - }); - this.messagingService.send("getWindowIsFocused"); - - // start background listener until destroyed on interval - this.timerId = setInterval(async () => { - this.supportsBiometric = await this.biometricsService.supportsBiometric(); - this.biometricReady = await this.canUseBiometric(); - }, 1000); - } - - ngOnDestroy() { - super.ngOnDestroy(); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - clearInterval(this.timerId); - } - - onWindowHidden() { - this.showPassword = false; - } - - private async delayedAskForBiometric(delay: number, params?: any) { - await new Promise((resolve) => setTimeout(resolve, delay)); - - if (params && !params.promptBiometric) { - return; - } - - if (!this.supportsBiometric || !this.autoPromptBiometric || this.biometricAsked) { - return; - } - - if (await firstValueFrom(this.biometricStateService.promptCancelled$)) { - return; - } - - this.biometricAsked = true; - if (await ipc.platform.isWindowVisible()) { - // 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.unlockBiometric(); - } - } - - private async canUseBiometric() { - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); - return await ipc.keyManagement.biometric.enabled(userId); - } - - private focusInput() { - document.getElementById(this.pinEnabled ? "pin" : "masterPassword")?.focus(); - } - - private async displayBiometricUpdateWarning(): Promise { - if (await firstValueFrom(this.biometricStateService.dismissedRequirePasswordOnStartCallout$)) { - return; - } - - if (this.platformUtilsService.getDevice() !== DeviceType.WindowsDesktop) { - return; - } - - if (await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)) { - const response = await this.dialogService.openSimpleDialog({ - title: { key: "windowsBiometricUpdateWarningTitle" }, - content: { key: "windowsBiometricUpdateWarning" }, - type: "warning", - }); - - await this.biometricStateService.setRequirePasswordOnStart(response); - if (response) { - await this.biometricStateService.setPromptAutomatically(false); - } - this.supportsBiometric = await this.canUseBiometric(); - await this.biometricStateService.setDismissedRequirePasswordOnStartCallout(); - } - } - - get biometricText() { - switch (this.platformUtilsService.getDevice()) { - case DeviceType.MacOsDesktop: - return "unlockWithTouchId"; - case DeviceType.WindowsDesktop: - return "unlockWithWindowsHello"; - case DeviceType.LinuxDesktop: - return "unlockWithPolkit"; - default: - throw new Error("Unsupported platform"); - } - } -} diff --git a/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts b/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts new file mode 100644 index 00000000000..d687ae35742 --- /dev/null +++ b/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts @@ -0,0 +1,89 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { Subject } from "rxjs"; + +import { LoginApprovalComponent } from "@bitwarden/auth/angular"; +import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { DesktopLoginApprovalComponentService } from "./desktop-login-approval-component.service"; + +describe("DesktopLoginApprovalComponentService", () => { + let service: DesktopLoginApprovalComponentService; + let i18nService: MockProxy; + let originalIpc: any; + + beforeEach(() => { + originalIpc = (global as any).ipc; + (global as any).ipc = { + auth: { + loginRequest: jest.fn(), + }, + platform: { + isWindowVisible: jest.fn(), + }, + }; + + i18nService = mock({ + t: jest.fn(), + userSetLocale$: new Subject(), + locale$: new Subject(), + }); + + TestBed.configureTestingModule({ + providers: [ + DesktopLoginApprovalComponentService, + { provide: I18nServiceAbstraction, useValue: i18nService }, + ], + }); + + service = TestBed.inject(DesktopLoginApprovalComponentService); + }); + + afterEach(() => { + jest.clearAllMocks(); + (global as any).ipc = originalIpc; + }); + + it("is created successfully", () => { + expect(service).toBeTruthy(); + }); + + it("calls ipc.auth.loginRequest with correct parameters when window is not visible", async () => { + const title = "Log in requested"; + const email = "test@bitwarden.com"; + const message = `Confirm login attempt for ${email}`; + const closeText = "Close"; + + const loginApprovalComponent = { email } as LoginApprovalComponent; + i18nService.t.mockImplementation((key: string) => { + switch (key) { + case "logInRequested": + return title; + case "confirmLoginAtemptForMail": + return message; + case "close": + return closeText; + default: + return ""; + } + }); + + jest.spyOn(ipc.platform, "isWindowVisible").mockResolvedValue(false); + jest.spyOn(ipc.auth, "loginRequest").mockResolvedValue(); + + await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalComponent.email); + + expect(ipc.auth.loginRequest).toHaveBeenCalledWith(title, message, closeText); + }); + + it("does not call ipc.auth.loginRequest when window is visible", async () => { + const loginApprovalComponent = { email: "test@bitwarden.com" } as LoginApprovalComponent; + + jest.spyOn(ipc.platform, "isWindowVisible").mockResolvedValue(true); + jest.spyOn(ipc.auth, "loginRequest"); + + await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalComponent.email); + + expect(ipc.auth.loginRequest).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts b/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts new file mode 100644 index 00000000000..9774dbba706 --- /dev/null +++ b/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts @@ -0,0 +1,28 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Injectable } from "@angular/core"; + +import { DefaultLoginApprovalComponentService } from "@bitwarden/auth/angular"; +import { LoginApprovalComponentServiceAbstraction } from "@bitwarden/auth/common"; +import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; + +@Injectable() +export class DesktopLoginApprovalComponentService + extends DefaultLoginApprovalComponentService + implements LoginApprovalComponentServiceAbstraction +{ + constructor(private i18nService: I18nServiceAbstraction) { + super(); + } + + async showLoginRequestedAlertIfWindowNotVisible(email: string): Promise { + const isVisible = await ipc.platform.isWindowVisible(); + if (!isVisible) { + await ipc.auth.loginRequest( + this.i18nService.t("logInRequested"), + this.i18nService.t("confirmLoginAtemptForMail", email), + this.i18nService.t("close"), + ); + } + } +} diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.ts b/apps/desktop/src/auth/login/desktop-login-component.service.ts index c9b01c5624f..dbf689e801c 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { DefaultLoginComponentService, LoginComponentService } from "@bitwarden/auth/angular"; @@ -63,6 +65,8 @@ export class DesktopLoginComponentService try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.toastService.showToast({ variant: "error", @@ -71,8 +75,4 @@ export class DesktopLoginComponentService }); } } - - isLoginViaAuthRequestSupported(): boolean { - return true; - } } diff --git a/apps/desktop/src/auth/login/login-approval.component.html b/apps/desktop/src/auth/login/login-approval.component.html deleted file mode 100644 index cc2c0536c9e..00000000000 --- a/apps/desktop/src/auth/login/login-approval.component.html +++ /dev/null @@ -1,42 +0,0 @@ - - {{ "areYouTryingtoLogin" | i18n }} - -

{{ "logInAttemptBy" | i18n: email }}

-
- {{ "fingerprintPhraseHeader" | i18n }} -

{{ fingerprintPhrase }}

-
-
- {{ "deviceType" | i18n }} -

{{ authRequestResponse?.requestDeviceType }}

-
-
- {{ "ipAddress" | i18n }} -

{{ authRequestResponse?.requestIpAddress }}

-
-
- {{ "time" | i18n }} -

{{ requestTimeText }}

-
-
- - - - -
diff --git a/apps/desktop/src/auth/login/login-decryption-options/login-decryption-options.component.html b/apps/desktop/src/auth/login/login-decryption-options/login-decryption-options-v1.component.html similarity index 100% rename from apps/desktop/src/auth/login/login-decryption-options/login-decryption-options.component.html rename to apps/desktop/src/auth/login/login-decryption-options/login-decryption-options-v1.component.html diff --git a/apps/desktop/src/auth/login/login-decryption-options/login-decryption-options.component.ts b/apps/desktop/src/auth/login/login-decryption-options/login-decryption-options-v1.component.ts similarity index 56% rename from apps/desktop/src/auth/login/login-decryption-options/login-decryption-options.component.ts rename to apps/desktop/src/auth/login/login-decryption-options/login-decryption-options-v1.component.ts index f64ec977ce7..d9cc07adb7e 100644 --- a/apps/desktop/src/auth/login/login-decryption-options/login-decryption-options.component.ts +++ b/apps/desktop/src/auth/login/login-decryption-options/login-decryption-options-v1.component.ts @@ -1,12 +1,12 @@ import { Component } from "@angular/core"; -import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component"; +import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component"; @Component({ selector: "desktop-login-decryption-options", - templateUrl: "login-decryption-options.component.html", + templateUrl: "login-decryption-options-v1.component.html", }) -export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent { +export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 { override async createUser(): Promise { try { await super.createUser(); diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts index 132b430f327..e0c3f794dba 100644 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ b/apps/desktop/src/auth/login/login-v1.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; @@ -247,6 +249,8 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.platformUtilsService.showToast( "error", diff --git a/apps/desktop/src/auth/login/login-via-auth-request.component.html b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html similarity index 100% rename from apps/desktop/src/auth/login/login-via-auth-request.component.html rename to apps/desktop/src/auth/login/login-via-auth-request-v1.component.html diff --git a/apps/desktop/src/auth/login/login-via-auth-request.component.ts b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.ts similarity index 91% rename from apps/desktop/src/auth/login/login-via-auth-request.component.ts rename to apps/desktop/src/auth/login/login-via-auth-request-v1.component.ts index 8459dc7441e..30e693b9ac6 100644 --- a/apps/desktop/src/auth/login/login-via-auth-request.component.ts +++ b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.ts @@ -1,8 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Location } from "@angular/common"; import { Component, ViewChild, ViewContainerRef } from "@angular/core"; import { Router } from "@angular/router"; -import { LoginViaAuthRequestComponent as BaseLoginWithDeviceComponent } from "@bitwarden/angular/auth/components/login-via-auth-request.component"; +import { LoginViaAuthRequestComponentV1 as BaseLoginViaAuthRequestComponentV1 } from "@bitwarden/angular/auth/components/login-via-auth-request-v1.component"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuthRequestServiceAbstraction, @@ -30,9 +32,9 @@ import { EnvironmentComponent } from "../environment.component"; @Component({ selector: "app-login-via-auth-request", - templateUrl: "login-via-auth-request.component.html", + templateUrl: "login-via-auth-request-v1.component.html", }) -export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { +export class LoginViaAuthRequestComponentV1 extends BaseLoginViaAuthRequestComponentV1 { @ViewChild("environment", { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef; showingModal = false; diff --git a/apps/desktop/src/auth/login/login.module.ts b/apps/desktop/src/auth/login/login.module.ts index c0b330bf2dd..427cbcb2069 100644 --- a/apps/desktop/src/auth/login/login.module.ts +++ b/apps/desktop/src/auth/login/login.module.ts @@ -5,18 +5,18 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components import { SharedModule } from "../../app/shared/shared.module"; -import { LoginDecryptionOptionsComponent } from "./login-decryption-options/login-decryption-options.component"; +import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "./login-v1.component"; -import { LoginViaAuthRequestComponent } from "./login-via-auth-request.component"; +import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component"; @NgModule({ imports: [SharedModule, RouterModule], declarations: [ LoginComponentV1, - LoginViaAuthRequestComponent, + LoginViaAuthRequestComponentV1, EnvironmentSelectorComponent, - LoginDecryptionOptionsComponent, + LoginDecryptionOptionsComponentV1, ], - exports: [LoginComponentV1, LoginViaAuthRequestComponent], + exports: [LoginComponentV1, LoginViaAuthRequestComponentV1], }) export class LoginModule {} diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index 61ab198b613..902fa59791c 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -9,7 +9,6 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -23,7 +22,7 @@ import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; const BroadcasterSubscriptionId = "SetPasswordComponent"; diff --git a/apps/desktop/src/auth/sso.component.html b/apps/desktop/src/auth/sso-v1.component.html similarity index 100% rename from apps/desktop/src/auth/sso.component.html rename to apps/desktop/src/auth/sso-v1.component.html diff --git a/apps/desktop/src/auth/sso.component.ts b/apps/desktop/src/auth/sso-v1.component.ts similarity index 97% rename from apps/desktop/src/auth/sso.component.ts rename to apps/desktop/src/auth/sso-v1.component.ts index 760eef14e80..da3139e31f7 100644 --- a/apps/desktop/src/auth/sso.component.ts +++ b/apps/desktop/src/auth/sso-v1.component.ts @@ -23,9 +23,9 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac @Component({ selector: "app-sso", - templateUrl: "sso.component.html", + templateUrl: "sso-v1.component.html", }) -export class SsoComponent extends BaseSsoComponent { +export class SsoComponentV1 extends BaseSsoComponent { constructor( ssoLoginService: SsoLoginServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction, diff --git a/apps/desktop/src/auth/two-factor-auth-duo.component.ts b/apps/desktop/src/auth/two-factor-auth-duo.component.ts index e22fd3ee612..c238b753b64 100644 --- a/apps/desktop/src/auth/two-factor-auth-duo.component.ts +++ b/apps/desktop/src/auth/two-factor-auth-duo.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; @@ -19,6 +21,8 @@ import { TypographyModule, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; const BroadcasterSubscriptionId = "TwoFactorComponent"; diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts index 29271b565c1..9e0898c39e2 100644 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ b/apps/desktop/src/auth/two-factor-auth.component.ts @@ -4,19 +4,47 @@ import { Component } from "@angular/core"; import { ReactiveFormsModule } from "@angular/forms"; import { RouterLink } from "@angular/router"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthEmailComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthWebAuthnComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthYubikeyComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { JslibModule } from "../../../../libs/angular/src/jslib.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CheckboxModule } from "../../../../libs/components/src/checkbox"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../libs/components/src/typography"; import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; diff --git a/apps/desktop/src/auth/two-factor.component.ts b/apps/desktop/src/auth/two-factor.component.ts index 0050ec65608..7f4525c5f14 100644 --- a/apps/desktop/src/auth/two-factor.component.ts +++ b/apps/desktop/src/auth/two-factor.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Inject, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts new file mode 100644 index 00000000000..494544f5858 --- /dev/null +++ b/apps/desktop/src/autofill/preload.ts @@ -0,0 +1,92 @@ +import { ipcRenderer } from "electron"; + +import type { autofill } from "@bitwarden/desktop-napi"; + +import { Command } from "../platform/main/autofill/command"; +import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; + +export default { + runCommand: (params: RunCommandParams): Promise> => + ipcRenderer.invoke("autofill.runCommand", params), + + listenPasskeyRegistration: ( + fn: ( + clientId: number, + sequenceNumber: number, + request: autofill.PasskeyRegistrationRequest, + completeCallback: ( + error: Error | null, + response: autofill.PasskeyRegistrationResponse, + ) => void, + ) => void, + ) => { + ipcRenderer.on( + "autofill.passkeyRegistration", + ( + event, + data: { + clientId: number; + sequenceNumber: number; + request: autofill.PasskeyRegistrationRequest; + }, + ) => { + const { clientId, sequenceNumber, request } = data; + fn(clientId, sequenceNumber, request, (error, response) => { + if (error) { + ipcRenderer.send("autofill.completeError", { + clientId, + sequenceNumber, + error: error.message, + }); + return; + } + + ipcRenderer.send("autofill.completePasskeyRegistration", { + clientId, + sequenceNumber, + response, + }); + }); + }, + ); + }, + + listenPasskeyAssertion: ( + fn: ( + clientId: number, + sequenceNumber: number, + request: autofill.PasskeyAssertionRequest, + completeCallback: (error: Error | null, response: autofill.PasskeyAssertionResponse) => void, + ) => void, + ) => { + ipcRenderer.on( + "autofill.passkeyAssertion", + ( + event, + data: { + clientId: number; + sequenceNumber: number; + request: autofill.PasskeyAssertionRequest; + }, + ) => { + const { clientId, sequenceNumber, request } = data; + fn(clientId, sequenceNumber, request, (error, response) => { + if (error) { + ipcRenderer.send("autofill.completeError", { + clientId, + sequenceNumber, + error: error.message, + }); + return; + } + + ipcRenderer.send("autofill.completePasskeyAssertion", { + clientId, + sequenceNumber, + response, + }); + }); + }, + ); + }, +}; diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts new file mode 100644 index 00000000000..1ce58596b34 --- /dev/null +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -0,0 +1,285 @@ +import { Injectable, OnDestroy } from "@angular/core"; +import { autofill } from "desktop_native/napi"; +import { + EMPTY, + Subject, + distinctUntilChanged, + firstValueFrom, + map, + mergeMap, + switchMap, + takeUntil, +} from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { + Fido2AuthenticatorGetAssertionParams, + Fido2AuthenticatorGetAssertionResult, + Fido2AuthenticatorMakeCredentialResult, + Fido2AuthenticatorMakeCredentialsParams, + Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction, +} from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils"; +import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils"; +import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { NativeAutofillStatusCommand } from "../../platform/main/autofill/status.command"; +import { + NativeAutofillFido2Credential, + NativeAutofillPasswordCredential, + NativeAutofillSyncCommand, +} from "../../platform/main/autofill/sync.command"; + +@Injectable() +export class DesktopAutofillService implements OnDestroy { + private destroy$ = new Subject(); + + constructor( + private logService: LogService, + private cipherService: CipherService, + private configService: ConfigService, + private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction, + private accountService: AccountService, + ) {} + + async init() { + this.configService + .getFeatureFlag$(FeatureFlag.MacOsNativeCredentialSync) + .pipe( + distinctUntilChanged(), + switchMap((enabled) => { + if (!enabled) { + return EMPTY; + } + + return this.cipherService.cipherViews$; + }), + // TODO: This will unset all the autofill credentials on the OS + // when the account locks. We should instead explicilty clear the credentials + // when the user logs out. Maybe by subscribing to the encrypted ciphers observable instead. + mergeMap((cipherViewMap) => this.sync(Object.values(cipherViewMap ?? []))), + takeUntil(this.destroy$), + ) + .subscribe(); + + this.listenIpc(); + } + + /** Give metadata about all available credentials in the users vault */ + async sync(cipherViews: CipherView[]) { + const status = await this.status(); + if (status.type === "error") { + return this.logService.error("Error getting autofill status", status.error); + } + + if (!status.value.state.enabled) { + // Autofill is disabled + return; + } + + let fido2Credentials: NativeAutofillFido2Credential[]; + let passwordCredentials: NativeAutofillPasswordCredential[]; + + if (status.value.support.password) { + passwordCredentials = cipherViews + .filter( + (cipher) => + cipher.type === CipherType.Login && + cipher.login.uris?.length > 0 && + cipher.login.uris.some((uri) => uri.match !== UriMatchStrategy.Never) && + cipher.login.uris.some((uri) => !Utils.isNullOrWhitespace(uri.uri)) && + !Utils.isNullOrWhitespace(cipher.login.username), + ) + .map((cipher) => ({ + type: "password", + cipherId: cipher.id, + uri: cipher.login.uris.find((uri) => uri.match !== UriMatchStrategy.Never).uri, + username: cipher.login.username, + })); + } + + if (status.value.support.fido2) { + fido2Credentials = (await getCredentialsForAutofill(cipherViews)).map((credential) => ({ + type: "fido2", + ...credential, + })); + } + + const syncResult = await ipc.autofill.runCommand({ + namespace: "autofill", + command: "sync", + params: { + credentials: [...fido2Credentials, ...passwordCredentials], + }, + }); + + if (syncResult.type === "error") { + return this.logService.error("Error syncing autofill credentials", syncResult.error); + } + + this.logService.debug(`Synced ${syncResult.value.added} autofill credentials`); + } + + /** Get autofill status from OS */ + private status() { + // TODO: Investigate why this type needs to be explicitly set + return ipc.autofill.runCommand({ + namespace: "autofill", + command: "status", + params: {}, + }); + } + + listenIpc() { + ipc.autofill.listenPasskeyRegistration((clientId, sequenceNumber, request, callback) => { + this.logService.warning("listenPasskeyRegistration", clientId, sequenceNumber, request); + this.logService.warning( + "listenPasskeyRegistration2", + this.convertRegistrationRequest(request), + ); + + const controller = new AbortController(); + void this.fido2AuthenticatorService + .makeCredential(this.convertRegistrationRequest(request), null, controller) + .then((response) => { + callback(null, this.convertRegistrationResponse(request, response)); + }) + .catch((error) => { + this.logService.error("listenPasskeyRegistration error", error); + callback(error, null); + }); + }); + + ipc.autofill.listenPasskeyAssertion(async (clientId, sequenceNumber, request, callback) => { + this.logService.warning("listenPasskeyAssertion", clientId, sequenceNumber, request); + + // TODO: For some reason the credentialId is passed as an empty array in the request, so we need to + // get it from the cipher. For that we use the recordIdentifier, which is the cipherId. + if (request.recordIdentifier && request.credentialId.length === 0) { + const cipher = await this.cipherService.get(request.recordIdentifier); + if (!cipher) { + this.logService.error("listenPasskeyAssertion error", "Cipher not found"); + callback(new Error("Cipher not found"), null); + return; + } + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const decrypted = await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + + const fido2Credential = decrypted.login.fido2Credentials?.[0]; + if (!fido2Credential) { + this.logService.error("listenPasskeyAssertion error", "Fido2Credential not found"); + callback(new Error("Fido2Credential not found"), null); + return; + } + + request.credentialId = Array.from( + guidToRawFormat(decrypted.login.fido2Credentials?.[0].credentialId), + ); + } + + const controller = new AbortController(); + void this.fido2AuthenticatorService + .getAssertion(this.convertAssertionRequest(request), null, controller) + .then((response) => { + callback(null, this.convertAssertionResponse(request, response)); + }) + .catch((error) => { + this.logService.error("listenPasskeyAssertion error", error); + callback(error, null); + }); + }); + } + + private convertRegistrationRequest( + request: autofill.PasskeyRegistrationRequest, + ): Fido2AuthenticatorMakeCredentialsParams { + return { + hash: new Uint8Array(request.clientDataHash), + rpEntity: { + name: request.rpId, + id: request.rpId, + }, + userEntity: { + id: new Uint8Array(request.userHandle), + name: request.userName, + displayName: undefined, + icon: undefined, + }, + credTypesAndPubKeyAlgs: request.supportedAlgorithms.map((alg) => ({ + alg, + type: "public-key", + })), + excludeCredentialDescriptorList: [], + requireResidentKey: true, + requireUserVerification: + request.userVerification === "required" || request.userVerification === "preferred", + fallbackSupported: false, + }; + } + + private convertRegistrationResponse( + request: autofill.PasskeyRegistrationRequest, + response: Fido2AuthenticatorMakeCredentialResult, + ): autofill.PasskeyRegistrationResponse { + return { + rpId: request.rpId, + clientDataHash: request.clientDataHash, + credentialId: Array.from(Fido2Utils.bufferSourceToUint8Array(response.credentialId)), + attestationObject: Array.from( + Fido2Utils.bufferSourceToUint8Array(response.attestationObject), + ), + }; + } + + private convertAssertionRequest( + request: autofill.PasskeyAssertionRequest, + ): Fido2AuthenticatorGetAssertionParams { + return { + rpId: request.rpId, + hash: new Uint8Array(request.clientDataHash), + allowCredentialDescriptorList: [ + { + id: new Uint8Array(request.credentialId), + type: "public-key", + }, + ], + extensions: {}, + requireUserVerification: + request.userVerification === "required" || request.userVerification === "preferred", + fallbackSupported: false, + }; + } + + private convertAssertionResponse( + request: autofill.PasskeyAssertionRequest, + response: Fido2AuthenticatorGetAssertionResult, + ): autofill.PasskeyAssertionResponse { + return { + userHandle: Array.from(response.selectedCredential.userHandle), + rpId: request.rpId, + signature: Array.from(response.signature), + clientDataHash: request.clientDataHash, + authenticatorData: Array.from(response.authenticatorData), + credentialId: Array.from(response.selectedCredential.id), + }; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts new file mode 100644 index 00000000000..684b19e7394 --- /dev/null +++ b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts @@ -0,0 +1,123 @@ +import { firstValueFrom, map } from "rxjs"; + +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 { + Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction, + Fido2UserInterfaceSession, + NewCredentialParams, + PickCredentialParams, +} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherRepromptType, CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; +import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; + +export class DesktopFido2UserInterfaceService + implements Fido2UserInterfaceServiceAbstraction +{ + constructor( + private authService: AuthService, + private cipherService: CipherService, + private accountService: AccountService, + private logService: LogService, + ) {} + + async newSession( + fallbackSupported: boolean, + _tab: void, + abortController?: AbortController, + ): Promise { + this.logService.warning("newSession", fallbackSupported, abortController); + return new DesktopFido2UserInterfaceSession( + this.authService, + this.cipherService, + this.accountService, + this.logService, + ); + } +} + +export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSession { + constructor( + private authService: AuthService, + private cipherService: CipherService, + private accountService: AccountService, + private logService: LogService, + ) {} + + async pickCredential({ + cipherIds, + userVerification, + }: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { + this.logService.warning("pickCredential", cipherIds, userVerification); + + return { cipherId: cipherIds[0], userVerified: userVerification }; + } + + async confirmNewCredential({ + credentialName, + userName, + userVerification, + rpId, + }: NewCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { + this.logService.warning( + "confirmNewCredential", + credentialName, + userName, + userVerification, + rpId, + ); + + // Store the passkey on a new cipher to avoid replacing something important + const cipher = new CipherView(); + cipher.name = credentialName; + + cipher.type = CipherType.Login; + cipher.login = new LoginView(); + cipher.login.username = userName; + cipher.login.uris = [new LoginUriView()]; + cipher.login.uris[0].uri = "https://" + rpId; + cipher.card = new CardView(); + cipher.identity = new IdentityView(); + cipher.secureNote = new SecureNoteView(); + cipher.secureNote.type = SecureNoteType.Generic; + cipher.reprompt = CipherRepromptType.None; + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const encCipher = await this.cipherService.encrypt(cipher, activeUserId); + const createdCipher = await this.cipherService.createWithServer(encCipher); + + return { cipherId: createdCipher.id, userVerified: userVerification }; + } + + async informExcludedCredential(existingCipherIds: string[]): Promise { + this.logService.warning("informExcludedCredential", existingCipherIds); + } + + async ensureUnlockedVault(): Promise { + this.logService.warning("ensureUnlockedVault"); + + const status = await firstValueFrom(this.authService.activeAccountStatus$); + if (status !== AuthenticationStatus.Unlocked) { + throw new Error("Vault is not unlocked"); + } + } + + async informCredentialNotFound(): Promise { + this.logService.warning("informCredentialNotFound"); + } + + async close() { + this.logService.warning("close"); + } +} diff --git a/apps/desktop/src/images/loading.svg b/apps/desktop/src/images/loading.svg index 70763105168..3f2033303db 100644 --- a/apps/desktop/src/images/loading.svg +++ b/apps/desktop/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/desktop/src/key-management/biometrics/biometric.noop.main.ts b/apps/desktop/src/key-management/biometrics/biometric.noop.main.ts deleted file mode 100644 index 57a86942e8c..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometric.noop.main.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { OsBiometricService } from "./desktop.biometrics.service"; - -export default class NoopBiometricsService implements OsBiometricService { - constructor() {} - - async init() {} - - async osSupportsBiometric(): Promise { - return false; - } - - async osBiometricsNeedsSetup(): Promise { - return false; - } - - async osBiometricsCanAutoSetup(): Promise { - return false; - } - - async osBiometricsSetup(): Promise {} - - async getBiometricKey( - service: string, - storageKey: string, - clientKeyHalfB64: string, - ): Promise { - return null; - } - - async setBiometricKey( - service: string, - storageKey: string, - value: string, - clientKeyPartB64: string | undefined, - ): Promise { - return; - } - - async deleteBiometricKey(service: string, key: string): Promise {} - - async authenticateBiometric(): Promise { - throw new Error("Not supported on this platform"); - } -} diff --git a/apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts deleted file mode 100644 index 48b41881bd2..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ipcMain } from "electron"; - -import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; - -import { BiometricMessage, BiometricAction } from "../../types/biometric-message"; - -import { DesktopBiometricsService } from "./desktop.biometrics.service"; - -export class BiometricsRendererIPCListener { - constructor( - private serviceName: string, - private biometricService: DesktopBiometricsService, - private logService: ConsoleLogService, - ) {} - - init() { - ipcMain.handle("biometric", async (event: any, message: BiometricMessage) => { - try { - let serviceName = this.serviceName; - message.keySuffix = "_" + (message.keySuffix ?? ""); - if (message.keySuffix !== "_") { - serviceName += message.keySuffix; - } - - let val: string | boolean = null; - - if (!message.action) { - return val; - } - - switch (message.action) { - case BiometricAction.EnabledForUser: - if (!message.key || !message.userId) { - break; - } - val = await this.biometricService.canAuthBiometric({ - service: serviceName, - key: message.key, - userId: message.userId, - }); - break; - case BiometricAction.OsSupported: - val = await this.biometricService.supportsBiometric(); - break; - case BiometricAction.NeedsSetup: - val = await this.biometricService.biometricsNeedsSetup(); - break; - case BiometricAction.Setup: - await this.biometricService.biometricsSetup(); - break; - case BiometricAction.CanAutoSetup: - val = await this.biometricService.biometricsSupportsAutoSetup(); - break; - default: - } - - return val; - } catch (e) { - this.logService.info(e); - } - }); - } -} diff --git a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts index d2ed648ba65..e69ebca3630 100644 --- a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts @@ -4,14 +4,19 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricStateService } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; import { WindowMain } from "../../main/window.main"; -import BiometricDarwinMain from "./biometric.darwin.main"; -import BiometricWindowsMain from "./biometric.windows.main"; -import { BiometricsService } from "./biometrics.service"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { MainBiometricsService } from "./main-biometrics.service"; +import OsBiometricsServiceLinux from "./os-biometrics-linux.service"; +import OsBiometricsServiceMac from "./os-biometrics-mac.service"; +import OsBiometricsServiceWindows from "./os-biometrics-windows.service"; +import { OsBiometricService } from "./os-biometrics.service"; jest.mock("@bitwarden/desktop-napi", () => { return { @@ -28,8 +33,7 @@ describe("biometrics tests", function () { const biometricStateService = mock(); it("Should call the platformspecific methods", async () => { - const userId = "userId-1" as UserId; - const sut = new BiometricsService( + const sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -39,21 +43,15 @@ describe("biometrics tests", function () { ); const mockService = mock(); - (sut as any).platformSpecificService = mockService; - await sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" }); + (sut as any).osBiometricsService = mockService; - await sut.canAuthBiometric({ service: "test", key: "test", userId }); - expect(mockService.osSupportsBiometric).toBeCalled(); - - // 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 - sut.authenticateBiometric(); + await sut.authenticateBiometric(); expect(mockService.authenticateBiometric).toBeCalled(); }); describe("Should create a platform specific service", function () { it("Should create a biometrics service specific for Windows", () => { - const sut = new BiometricsService( + const sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -62,13 +60,13 @@ describe("biometrics tests", function () { biometricStateService, ); - const internalService = (sut as any).platformSpecificService; + const internalService = (sut as any).osBiometricsService; expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(BiometricWindowsMain); + expect(internalService).toBeInstanceOf(OsBiometricsServiceWindows); }); it("Should create a biometrics service specific for MacOs", () => { - const sut = new BiometricsService( + const sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -76,19 +74,33 @@ describe("biometrics tests", function () { "darwin", biometricStateService, ); - const internalService = (sut as any).platformSpecificService; + const internalService = (sut as any).osBiometricsService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(OsBiometricsServiceMac); + }); + + it("Should create a biometrics service specific for Linux", () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + "linux", + biometricStateService, + ); + + const internalService = (sut as any).osBiometricsService; expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(BiometricDarwinMain); + expect(internalService).toBeInstanceOf(OsBiometricsServiceLinux); }); }); describe("can auth biometric", () => { let sut: BiometricsService; let innerService: MockProxy; - const userId = "userId-1" as UserId; beforeEach(() => { - sut = new BiometricsService( + sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -98,34 +110,78 @@ describe("biometrics tests", function () { ); innerService = mock(); - (sut as any).platformSpecificService = innerService; + (sut as any).osBiometricsService = innerService; }); - it("should return false if client key half is required and not provided", async () => { - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); - - const result = await sut.canAuthBiometric({ service: "test", key: "test", userId }); - - expect(result).toBe(false); + it("should return the correct biometric status for system status", async () => { + const testCases = [ + // happy path + [true, false, false, BiometricsStatus.Available], + [false, true, true, BiometricsStatus.AutoSetupNeeded], + [false, true, false, BiometricsStatus.ManualSetupNeeded], + [false, false, false, BiometricsStatus.HardwareUnavailable], + + // should not happen + [false, false, true, BiometricsStatus.HardwareUnavailable], + [true, true, true, BiometricsStatus.Available], + [true, true, false, BiometricsStatus.Available], + [true, false, true, BiometricsStatus.Available], + ]; + + for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) { + innerService.osSupportsBiometric.mockResolvedValue(supportsBiometric as boolean); + innerService.osBiometricsNeedsSetup.mockResolvedValue(needsSetup as boolean); + innerService.osBiometricsCanAutoSetup.mockResolvedValue(canAutoSetup as boolean); + + const actual = await sut.getBiometricsStatus(); + expect(actual).toBe(expected); + } }); - it("should call osSupportsBiometric if client key half is provided", async () => { - // 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 - sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" }); - - await sut.canAuthBiometric({ service: "test", key: "test", userId }); - expect(innerService.osSupportsBiometric).toBeCalled(); - }); - - it("should call osSupportBiometric if client key half is not required", async () => { - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(false); - innerService.osSupportsBiometric.mockResolvedValue(true); - - const result = await sut.canAuthBiometric({ service: "test", key: "test", userId }); - - expect(result).toBe(true); - expect(innerService.osSupportsBiometric).toHaveBeenCalled(); + it("should return the correct biometric status for user status", async () => { + const testCases = [ + // system status, biometric unlock enabled, require password on start, has key half, result + [BiometricsStatus.Available, false, false, false, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, true, false, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, false, true, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, true, true, BiometricsStatus.NotEnabledLocally], + + [ + BiometricsStatus.PlatformUnsupported, + true, + true, + true, + BiometricsStatus.PlatformUnsupported, + ], + [BiometricsStatus.ManualSetupNeeded, true, true, true, BiometricsStatus.ManualSetupNeeded], + [BiometricsStatus.AutoSetupNeeded, true, true, true, BiometricsStatus.AutoSetupNeeded], + + [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], + [BiometricsStatus.Available, true, true, false, BiometricsStatus.UnlockNeeded], + [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], + ]; + + for (const [ + systemStatus, + unlockEnabled, + requirePasswordOnStart, + hasKeyHalf, + expected, + ] of testCases) { + sut.getBiometricsStatus = jest.fn().mockResolvedValue(systemStatus as BiometricsStatus); + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(unlockEnabled as boolean); + biometricStateService.getRequirePasswordOnStart.mockResolvedValue( + requirePasswordOnStart as boolean, + ); + (sut as any).clientKeyHalves = new Map(); + const userId = "test" as UserId; + if (hasKeyHalf) { + (sut as any).clientKeyHalves.set(userId, "test"); + } + + const actual = await sut.getBiometricsStatusForUser(userId); + expect(actual).toBe(expected); + } }); }); }); diff --git a/apps/desktop/src/key-management/biometrics/biometrics.service.ts b/apps/desktop/src/key-management/biometrics/biometrics.service.ts deleted file mode 100644 index e7e0773ad16..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometrics.service.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricStateService } from "@bitwarden/key-management"; - -import { WindowMain } from "../../main/window.main"; - -import { DesktopBiometricsService, OsBiometricService } from "./desktop.biometrics.service"; - -export class BiometricsService extends DesktopBiometricsService { - private platformSpecificService: OsBiometricService; - private clientKeyHalves = new Map(); - - constructor( - private i18nService: I18nService, - private windowMain: WindowMain, - private logService: LogService, - private messagingService: MessagingService, - private platform: NodeJS.Platform, - private biometricStateService: BiometricStateService, - ) { - super(); - this.loadPlatformSpecificService(this.platform); - } - - private loadPlatformSpecificService(platform: NodeJS.Platform) { - if (platform === "win32") { - this.loadWindowsHelloService(); - } else if (platform === "darwin") { - this.loadMacOSService(); - } else if (platform === "linux") { - this.loadUnixService(); - } else { - this.loadNoopBiometricsService(); - } - } - - private loadWindowsHelloService() { - // eslint-disable-next-line - const BiometricWindowsMain = require("./biometric.windows.main").default; - this.platformSpecificService = new BiometricWindowsMain( - this.i18nService, - this.windowMain, - this.logService, - ); - } - - private loadMacOSService() { - // eslint-disable-next-line - const BiometricDarwinMain = require("./biometric.darwin.main").default; - this.platformSpecificService = new BiometricDarwinMain(this.i18nService); - } - - private loadUnixService() { - // eslint-disable-next-line - const BiometricUnixMain = require("./biometric.unix.main").default; - this.platformSpecificService = new BiometricUnixMain(this.i18nService, this.windowMain); - } - - private loadNoopBiometricsService() { - // eslint-disable-next-line - const NoopBiometricsService = require("./biometric.noop.main").default; - this.platformSpecificService = new NoopBiometricsService(); - } - - async supportsBiometric() { - return await this.platformSpecificService.osSupportsBiometric(); - } - - async biometricsNeedsSetup() { - return await this.platformSpecificService.osBiometricsNeedsSetup(); - } - - async biometricsSupportsAutoSetup() { - return await this.platformSpecificService.osBiometricsCanAutoSetup(); - } - - async biometricsSetup() { - await this.platformSpecificService.osBiometricsSetup(); - } - - async canAuthBiometric({ - service, - key, - userId, - }: { - service: string; - key: string; - userId: UserId; - }): Promise { - const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); - const clientKeyHalfB64 = this.getClientKeyHalf(service, key); - const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64; - return clientKeyHalfSatisfied && (await this.supportsBiometric()); - } - - async authenticateBiometric(): Promise { - let result = false; - // 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.interruptProcessReload( - () => { - return this.platformSpecificService.authenticateBiometric(); - }, - (response) => { - result = response; - return !response; - }, - ); - return result; - } - - async isBiometricUnlockAvailable(): Promise { - return await this.platformSpecificService.osSupportsBiometric(); - } - - async getBiometricKey(service: string, storageKey: string): Promise { - return await this.interruptProcessReload(async () => { - await this.enforceClientKeyHalf(service, storageKey); - - return await this.platformSpecificService.getBiometricKey( - service, - storageKey, - this.getClientKeyHalf(service, storageKey), - ); - }); - } - - async setBiometricKey(service: string, storageKey: string, value: string): Promise { - await this.enforceClientKeyHalf(service, storageKey); - - return await this.platformSpecificService.setBiometricKey( - service, - storageKey, - value, - this.getClientKeyHalf(service, storageKey), - ); - } - - /** Registers the client-side encryption key half for the OS stored Biometric key. The other half is protected by the OS.*/ - async setEncryptionKeyHalf({ - service, - key, - value, - }: { - service: string; - key: string; - value: string; - }): Promise { - if (value == null) { - this.clientKeyHalves.delete(this.clientKeyHalfKey(service, key)); - } else { - this.clientKeyHalves.set(this.clientKeyHalfKey(service, key), value); - } - } - - async deleteBiometricKey(service: string, storageKey: string): Promise { - this.clientKeyHalves.delete(this.clientKeyHalfKey(service, storageKey)); - return await this.platformSpecificService.deleteBiometricKey(service, storageKey); - } - - private async interruptProcessReload( - callback: () => Promise, - restartReloadCallback: (arg: T) => boolean = () => false, - ): Promise { - this.messagingService.send("cancelProcessReload"); - let restartReload = false; - let response: T; - try { - response = await callback(); - restartReload ||= restartReloadCallback(response); - } catch (error) { - if (error.message === "Biometric authentication failed") { - restartReload = false; - } else { - restartReload = true; - } - } - - if (restartReload) { - this.messagingService.send("startProcessReload"); - } - - return response; - } - - private clientKeyHalfKey(service: string, key: string): string { - return `${service}:${key}`; - } - - private getClientKeyHalf(service: string, key: string): string | undefined { - return this.clientKeyHalves.get(this.clientKeyHalfKey(service, key)) ?? undefined; - } - - private async enforceClientKeyHalf(service: string, storageKey: string): Promise { - // The first half of the storageKey is the userId, separated by `_` - // We need to extract from the service because the active user isn't properly synced to the main process, - // So we can't use the observables on `biometricStateService` - const [userId] = storageKey.split("_"); - const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart( - userId as UserId, - ); - const clientKeyHalfB64 = this.getClientKeyHalf(service, storageKey); - - if (requireClientKeyHalf && !clientKeyHalfB64) { - throw new Error("Biometric key requirements not met. No client key half provided."); - } - } -} diff --git a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts index eee3e5fc7f3..0c0efea78f9 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -1,3 +1,4 @@ +import { UserId } from "@bitwarden/common/types/guid"; import { BiometricsService } from "@bitwarden/key-management"; /** @@ -5,58 +6,10 @@ import { BiometricsService } from "@bitwarden/key-management"; * specifically for the main process. */ export abstract class DesktopBiometricsService extends BiometricsService { - abstract canAuthBiometric({ - service, - key, - userId, - }: { - service: string; - key: string; - userId: string; - }): Promise; - abstract getBiometricKey(service: string, key: string): Promise; - abstract setBiometricKey(service: string, key: string, value: string): Promise; - abstract setEncryptionKeyHalf({ - service, - key, - value, - }: { - service: string; - key: string; - value: string; - }): void; - abstract deleteBiometricKey(service: string, key: string): Promise; -} + abstract setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise; + abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise; + + abstract setupBiometrics(): Promise; -export interface OsBiometricService { - osSupportsBiometric(): Promise; - /** - * Check whether support for biometric unlock requires setup. This can be automatic or manual. - * - * @returns true if biometrics support requires setup, false if it does not (is already setup, or did not require it in the first place) - */ - osBiometricsNeedsSetup: () => Promise; - /** - * Check whether biometrics can be automatically setup, or requires user interaction. - * - * @returns true if biometrics support can be automatically setup, false if it requires user interaction. - */ - osBiometricsCanAutoSetup: () => Promise; - /** - * Starts automatic biometric setup, which places the required configuration files / changes the required settings. - */ - osBiometricsSetup: () => Promise; - authenticateBiometric(): Promise; - getBiometricKey( - service: string, - key: string, - clientKeyHalfB64: string | undefined, - ): Promise; - setBiometricKey( - service: string, - key: string, - value: string, - clientKeyHalfB64: string | undefined, - ): Promise; - deleteBiometricKey(service: string, key: string): Promise; + abstract setClientKeyHalfForUser(userId: UserId, value: string): Promise; } diff --git a/apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts deleted file mode 100644 index 226c914e6ff..00000000000 --- a/apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { BiometricsService } from "@bitwarden/key-management"; - -/** - * This service implement the base biometrics service to provide desktop specific functions, - * specifically for the renderer process by passing messages to the main process. - */ -@Injectable() -export class ElectronBiometricsService extends BiometricsService { - async supportsBiometric(): Promise { - return await ipc.keyManagement.biometric.osSupported(); - } - - async isBiometricUnlockAvailable(): Promise { - return await ipc.keyManagement.biometric.osSupported(); - } - - /** This method is used to authenticate the user presence _only_. - * It should not be used in the process to retrieve - * biometric keys, which has a separate authentication mechanism. - * For biometric keys, invoke "keytar" with a biometric key suffix */ - async authenticateBiometric(): Promise { - return await ipc.keyManagement.biometric.authenticate(); - } - - async biometricsNeedsSetup(): Promise { - return await ipc.keyManagement.biometric.biometricsNeedsSetup(); - } - - async biometricsSupportsAutoSetup(): Promise { - return await ipc.keyManagement.biometric.biometricsCanAutoSetup(); - } - - async biometricsSetup(): Promise { - return await ipc.keyManagement.biometric.biometricsSetup(); - } -} diff --git a/apps/desktop/src/key-management/biometrics/index.ts b/apps/desktop/src/key-management/biometrics/index.ts deleted file mode 100644 index ad7725d718a..00000000000 --- a/apps/desktop/src/key-management/biometrics/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./desktop.biometrics.service"; -export * from "./biometrics.service"; diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts new file mode 100644 index 00000000000..eebafd8d48b --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts @@ -0,0 +1,63 @@ +import { ipcMain } from "electron"; + +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { BiometricMessage, BiometricAction } from "../../types/biometric-message"; + +import { DesktopBiometricsService } from "./desktop.biometrics.service"; + +export class MainBiometricsIPCListener { + constructor( + private biometricService: DesktopBiometricsService, + private logService: ConsoleLogService, + ) {} + + init() { + ipcMain.handle("biometric", async (event: any, message: BiometricMessage) => { + try { + if (!message.action) { + return; + } + + switch (message.action) { + case BiometricAction.Authenticate: + return await this.biometricService.authenticateWithBiometrics(); + case BiometricAction.GetStatus: + return await this.biometricService.getBiometricsStatus(); + case BiometricAction.UnlockForUser: + return await this.biometricService.unlockWithBiometricsForUser( + message.userId as UserId, + ); + case BiometricAction.GetStatusForUser: + return await this.biometricService.getBiometricsStatusForUser(message.userId as UserId); + case BiometricAction.SetKeyForUser: + return await this.biometricService.setBiometricProtectedUnlockKeyForUser( + message.userId as UserId, + message.key, + ); + case BiometricAction.RemoveKeyForUser: + return await this.biometricService.deleteBiometricUnlockKeyForUser( + message.userId as UserId, + ); + case BiometricAction.SetClientKeyHalf: + return await this.biometricService.setClientKeyHalfForUser( + message.userId as UserId, + message.key, + ); + case BiometricAction.Setup: + return await this.biometricService.setupBiometrics(); + + case BiometricAction.SetShouldAutoprompt: + return await this.biometricService.setShouldAutopromptNow(message.data as boolean); + case BiometricAction.GetShouldAutoprompt: + return await this.biometricService.getShouldAutopromptNow(); + default: + return; + } + } catch (e) { + this.logService.info(e); + } + }); + } +} diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts new file mode 100644 index 00000000000..06956503a05 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -0,0 +1,167 @@ +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management"; + +import { WindowMain } from "../../main/window.main"; + +import { DesktopBiometricsService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; + +export class MainBiometricsService extends DesktopBiometricsService { + private osBiometricsService: OsBiometricService; + private clientKeyHalves = new Map(); + private shouldAutoPrompt = true; + + constructor( + private i18nService: I18nService, + private windowMain: WindowMain, + private logService: LogService, + private messagingService: MessagingService, + private platform: NodeJS.Platform, + private biometricStateService: BiometricStateService, + ) { + super(); + this.loadOsBiometricService(this.platform); + } + + private loadOsBiometricService(platform: NodeJS.Platform) { + if (platform === "win32") { + // eslint-disable-next-line + const OsBiometricsServiceWindows = require("./os-biometrics-windows.service").default; + this.osBiometricsService = new OsBiometricsServiceWindows( + this.i18nService, + this.windowMain, + this.logService, + ); + } else if (platform === "darwin") { + // eslint-disable-next-line + const OsBiometricsServiceMac = require("./os-biometrics-mac.service").default; + this.osBiometricsService = new OsBiometricsServiceMac(this.i18nService); + } else if (platform === "linux") { + // eslint-disable-next-line + const OsBiometricsServiceLinux = require("./os-biometrics-linux.service").default; + this.osBiometricsService = new OsBiometricsServiceLinux(this.i18nService, this.windowMain); + } else { + throw new Error("Unsupported platform"); + } + } + + /** + * Get the status of biometrics for the platform. Biometrics status for the platform can be one of: + * - Available: Biometrics are available and can be used (On windows hello, (touch id (for now)) and polkit, this MAY fall back to password) + * - HardwareUnavailable: Biometrics are not available on the platform + * - ManualSetupNeeded: In order to use biometrics, the user must perform manual steps (linux only) + * - AutoSetupNeeded: In order to use biometrics, the user must perform automatic steps (linux only) + * @returns the status of the biometrics of the platform + */ + async getBiometricsStatus(): Promise { + if (!(await this.osBiometricsService.osSupportsBiometric())) { + if (await this.osBiometricsService.osBiometricsNeedsSetup()) { + if (await this.osBiometricsService.osBiometricsCanAutoSetup()) { + return BiometricsStatus.AutoSetupNeeded; + } else { + return BiometricsStatus.ManualSetupNeeded; + } + } + + return BiometricsStatus.HardwareUnavailable; + } + return BiometricsStatus.Available; + } + + /** + * Get the status of biometric unlock for a specific user. For this, biometric unlock needs to be set up for the user in the settings. + * Next, biometrics unlock needs to be available on the platform level. If "masterpassword reprompt" is enabled, a client key half (set on first unlock) for this user + * needs to be held in memory. + * @param userId the user to check the biometric unlock status for + * @returns the status of the biometric unlock for the user + */ + async getBiometricsStatusForUser(userId: UserId): Promise { + if (!(await this.biometricStateService.getBiometricUnlockEnabled(userId))) { + return BiometricsStatus.NotEnabledLocally; + } + + const platformStatus = await this.getBiometricsStatus(); + if (!(platformStatus === BiometricsStatus.Available)) { + return platformStatus; + } + + const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); + const clientKeyHalfB64 = this.clientKeyHalves.get(userId); + const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64; + if (!clientKeyHalfSatisfied) { + return BiometricsStatus.UnlockNeeded; + } + + return BiometricsStatus.Available; + } + + async authenticateBiometric(): Promise { + return await this.osBiometricsService.authenticateBiometric(); + } + + async setupBiometrics(): Promise { + return await this.osBiometricsService.osBiometricsSetup(); + } + + async setClientKeyHalfForUser(userId: UserId, value: string): Promise { + this.clientKeyHalves.set(userId, value); + } + + async authenticateWithBiometrics(): Promise { + return await this.osBiometricsService.authenticateBiometric(); + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return SymmetricCryptoKey.fromString( + await this.osBiometricsService.getBiometricKey( + "Bitwarden_biometric", + `${userId}_user_biometric`, + this.clientKeyHalves.get(userId), + ), + ) as UserKey; + } + + async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { + const service = "Bitwarden_biometric"; + const storageKey = `${userId}_user_biometric`; + if (!this.clientKeyHalves.has(userId)) { + throw new Error("No client key half provided for user"); + } + + return await this.osBiometricsService.setBiometricKey( + service, + storageKey, + value, + this.clientKeyHalves.get(userId), + ); + } + + async deleteBiometricUnlockKeyForUser(userId: UserId): Promise { + return await this.osBiometricsService.deleteBiometricKey( + "Bitwarden_biometric", + `${userId}_user_biometric`, + ); + } + + /** + * Set whether to auto-prompt the user for biometric unlock; this can be used to prevent auto-prompting being initiated by a process reload. + * Reasons for enabling auto prompt include: Starting the app, un-minimizing the app, manually account switching + * @param value Whether to auto-prompt the user for biometric unlock + */ + async setShouldAutopromptNow(value: boolean): Promise { + this.shouldAutoPrompt = value; + } + + /** + * Get whether to auto-prompt the user for biometric unlock; If the user is auto-prompted, setShouldAutopromptNow should be immediately called with false in order to prevent another auto-prompt. + * @returns Whether to auto-prompt the user for biometric unlock + */ + async getShouldAutopromptNow(): Promise { + return this.shouldAutoPrompt; + } +} diff --git a/apps/desktop/src/key-management/biometrics/biometric.unix.main.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts similarity index 92% rename from apps/desktop/src/key-management/biometrics/biometric.unix.main.ts rename to apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts index 8962e7f3ecf..791b4d6f885 100644 --- a/apps/desktop/src/key-management/biometrics/biometric.unix.main.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { spawn } from "child_process"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -7,7 +9,7 @@ import { biometrics, passwords } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../main/window.main"; import { isFlatpak, isLinux, isSnapStore } from "../../utils"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; const polkitPolicy = ` const policyFileName = "com.bitwarden.Bitwarden.policy"; const policyPath = "/usr/share/polkit-1/actions/"; -export default class BiometricUnixMain implements OsBiometricService { +export default class OsBiometricsServiceLinux implements OsBiometricService { constructor( private i18nservice: I18nService, private windowMain: WindowMain, @@ -85,8 +87,8 @@ export default class BiometricUnixMain implements OsBiometricService { } async authenticateBiometric(): Promise { - const hwnd = this.windowMain.win.getNativeWindowHandle(); - return await biometrics.prompt(hwnd, this.i18nservice.t("polkitConsentMessage")); + const hwnd = Buffer.from(""); + return await biometrics.prompt(hwnd, ""); } async osSupportsBiometric(): Promise { @@ -96,10 +98,14 @@ export default class BiometricUnixMain implements OsBiometricService { // This could be dynamically detected on dbus in the future. // We should check if a libsecret implementation is available on the system // because otherwise we cannot offlod the protected userkey to secure storage. - return (await passwords.isAvailable()) && !isSnapStore(); + return await passwords.isAvailable(); } async osBiometricsNeedsSetup(): Promise { + if (isSnapStore()) { + return false; + } + // check whether the polkit policy is loaded via dbus call to polkit return !(await biometrics.available()); } diff --git a/apps/desktop/src/key-management/biometrics/biometric.darwin.main.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-mac.service.ts similarity index 92% rename from apps/desktop/src/key-management/biometrics/biometric.darwin.main.ts rename to apps/desktop/src/key-management/biometrics/os-biometrics-mac.service.ts index 0f26cc78fbf..e361084726a 100644 --- a/apps/desktop/src/key-management/biometrics/biometric.darwin.main.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-mac.service.ts @@ -3,9 +3,9 @@ import { systemPreferences } from "electron"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { passwords } from "@bitwarden/desktop-napi"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; -export default class BiometricDarwinMain implements OsBiometricService { +export default class OsBiometricsServiceMac implements OsBiometricService { constructor(private i18nservice: I18nService) {} async osSupportsBiometric(): Promise { diff --git a/apps/desktop/src/key-management/biometrics/biometric.windows.main.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts similarity index 91% rename from apps/desktop/src/key-management/biometrics/biometric.windows.main.ts rename to apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts index abda9bf9484..9643c2b6f15 100644 --- a/apps/desktop/src/key-management/biometrics/biometric.windows.main.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -6,12 +8,12 @@ import { biometrics, passwords } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../main/window.main"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; const KEY_WITNESS_SUFFIX = "_witness"; const WITNESS_VALUE = "known key"; -export default class BiometricWindowsMain implements OsBiometricService { +export default class OsBiometricsServiceWindows implements OsBiometricService { // Use set helper method instead of direct access private _iv: string | null = null; // Use getKeyMaterial helper instead of direct access @@ -111,13 +113,19 @@ export default class BiometricWindowsMain implements OsBiometricService { this._iv = keyMaterial.ivB64; } - return { + const result = { key_material: { osKeyPartB64: this._osKeyHalf, clientKeyPartB64: clientKeyHalfB64, }, ivB64: this._iv, }; + + // napi-rs fails to convert null values + if (result.key_material.clientKeyPartB64 == null) { + delete result.key_material.clientKeyPartB64; + } + return result; } // Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey @@ -209,10 +217,17 @@ export default class BiometricWindowsMain implements OsBiometricService { clientKeyPartB64: string, ): biometrics.KeyMaterial { const key = symmetricKey?.macKeyB64 ?? symmetricKey?.keyB64; - return { + + const result = { osKeyPartB64: key, clientKeyPartB64, }; + + // napi-rs fails to convert null values + if (result.clientKeyPartB64 == null) { + delete result.clientKeyPartB64; + } + return result; } async osBiometricsNeedsSetup() { diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics.service.ts new file mode 100644 index 00000000000..f5132200149 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/os-biometrics.service.ts @@ -0,0 +1,32 @@ +export interface OsBiometricService { + osSupportsBiometric(): Promise; + /** + * Check whether support for biometric unlock requires setup. This can be automatic or manual. + * + * @returns true if biometrics support requires setup, false if it does not (is already setup, or did not require it in the first place) + */ + osBiometricsNeedsSetup: () => Promise; + /** + * Check whether biometrics can be automatically setup, or requires user interaction. + * + * @returns true if biometrics support can be automatically setup, false if it requires user interaction. + */ + osBiometricsCanAutoSetup: () => Promise; + /** + * Starts automatic biometric setup, which places the required configuration files / changes the required settings. + */ + osBiometricsSetup: () => Promise; + authenticateBiometric(): Promise; + getBiometricKey( + service: string, + key: string, + clientKeyHalfB64: string | undefined, + ): Promise; + setBiometricKey( + service: string, + key: string, + value: string, + clientKeyHalfB64: string | undefined, + ): Promise; + deleteBiometricKey(service: string, key: string): Promise; +} diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts new file mode 100644 index 00000000000..a08e68b53f2 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from "@angular/core"; + +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsStatus } from "@bitwarden/key-management"; + +import { DesktopBiometricsService } from "./desktop.biometrics.service"; + +/** + * This service implement the base biometrics service to provide desktop specific functions, + * specifically for the renderer process by passing messages to the main process. + */ +@Injectable() +export class RendererBiometricsService extends DesktopBiometricsService { + async authenticateWithBiometrics(): Promise { + return await ipc.keyManagement.biometric.authenticateWithBiometrics(); + } + + async getBiometricsStatus(): Promise { + return await ipc.keyManagement.biometric.getBiometricsStatus(); + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return await ipc.keyManagement.biometric.unlockWithBiometricsForUser(userId); + } + + async getBiometricsStatusForUser(id: UserId): Promise { + return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id); + } + + async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { + return await ipc.keyManagement.biometric.setBiometricProtectedUnlockKeyForUser(userId, value); + } + + async deleteBiometricUnlockKeyForUser(userId: UserId): Promise { + return await ipc.keyManagement.biometric.deleteBiometricUnlockKeyForUser(userId); + } + + async setupBiometrics(): Promise { + return await ipc.keyManagement.biometric.setupBiometrics(); + } + + async setClientKeyHalfForUser(userId: UserId, value: string): Promise { + return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value); + } + + async getShouldAutopromptNow(): Promise { + return await ipc.keyManagement.biometric.getShouldAutoprompt(); + } + + async setShouldAutopromptNow(value: boolean): Promise { + return await ipc.keyManagement.biometric.setShouldAutoprompt(value); + } +} diff --git a/apps/desktop/src/services/desktop-lock-component.service.spec.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts similarity index 64% rename from apps/desktop/src/services/desktop-lock-component.service.spec.ts rename to apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts index 0d673a5b51c..2cc8d770f58 100644 --- a/apps/desktop/src/services/desktop-lock-component.service.spec.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts @@ -2,7 +2,6 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/auth/angular"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, @@ -11,7 +10,8 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul import { DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; +import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { UnlockOptions } from "@bitwarden/key-management/angular"; import { DesktopLockComponentService } from "./desktop-lock-component.service"; @@ -140,11 +140,7 @@ describe("DesktopLockComponentService", () => { describe("getAvailableUnlockOptions$", () => { interface MockInputs { hasMasterPassword: boolean; - osSupportsBiometric: boolean; - biometricLockSet: boolean; - biometricReady: boolean; - hasBiometricEncryptedUserKeyStored: boolean; - platformSupportsSecureStorage: boolean; + biometricsStatus: BiometricsStatus; pinDecryptionAvailable: boolean; } @@ -153,11 +149,7 @@ describe("DesktopLockComponentService", () => { // MP + PIN + Biometrics available { hasMasterPassword: true, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.Available, pinDecryptionAvailable: true, }, { @@ -169,7 +161,7 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -177,11 +169,7 @@ describe("DesktopLockComponentService", () => { // PIN + Biometrics available { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.Available, pinDecryptionAvailable: true, }, { @@ -193,67 +181,16 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, - }, - }, - ], - [ - // Biometrics available: user key stored with no secure storage - { - hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: false, - pinDecryptionAvailable: false, - }, - { - masterPassword: { - enabled: false, - }, - pin: { - enabled: false, - }, - biometrics: { - enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], [ // Biometrics available: no user key stored with no secure storage + // Biometric auth is available, but not unlock since there is no way to access the userkey { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: false, - biometricReady: true, - platformSupportsSecureStorage: false, - pinDecryptionAvailable: false, - }, - { - masterPassword: { - enabled: false, - }, - pin: { - enabled: false, - }, - biometrics: { - enabled: true, - disableReason: null, - }, - }, - ], - [ - // Biometrics not available: biometric not ready - { - hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: false, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.NotEnabledLocally, pinDecryptionAvailable: false, }, { @@ -265,43 +202,15 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.SystemBiometricsUnavailable, + biometricsStatus: BiometricsStatus.NotEnabledLocally, }, }, ], [ - // Biometrics not available: biometric lock not set - { - hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: false, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, - pinDecryptionAvailable: false, - }, - { - masterPassword: { - enabled: false, - }, - pin: { - enabled: false, - }, - biometrics: { - enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, - }, - }, - ], - [ - // Biometrics not available: user key not stored + // Biometrics not available: biometric not ready { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: false, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.HardwareUnavailable, pinDecryptionAvailable: false, }, { @@ -313,7 +222,7 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, + biometricsStatus: BiometricsStatus.HardwareUnavailable, }, }, ], @@ -321,11 +230,7 @@ describe("DesktopLockComponentService", () => { // Biometrics not available: OS doesn't support { hasMasterPassword: false, - osSupportsBiometric: false, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.PlatformUnsupported, pinDecryptionAvailable: false, }, { @@ -337,7 +242,7 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.NotSupportedOnOperatingSystem, + biometricsStatus: BiometricsStatus.PlatformUnsupported, }, }, ], @@ -355,13 +260,8 @@ describe("DesktopLockComponentService", () => { ); // Biometrics - biometricsService.supportsBiometric.mockResolvedValue(mockInputs.osSupportsBiometric); - vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(mockInputs.biometricLockSet); - keyService.hasUserKeyStored.mockResolvedValue(mockInputs.hasBiometricEncryptedUserKeyStored); - platformUtilsService.supportsSecureStorage.mockReturnValue( - mockInputs.platformSupportsSecureStorage, - ); - biometricEnabledMock.mockResolvedValue(mockInputs.biometricReady); + // TODO: FIXME + biometricsService.getBiometricsStatusForUser.mockResolvedValue(mockInputs.biometricsStatus); // PIN pinService.isPinDecryptionAvailable.mockResolvedValue(mockInputs.pinDecryptionAvailable); diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts new file mode 100644 index 00000000000..1d2d68c1d97 --- /dev/null +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts @@ -0,0 +1,72 @@ +import { inject } from "@angular/core"; +import { combineLatest, defer, map, Observable } from "rxjs"; + +import { + PinServiceAbstraction, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { DeviceType } from "@bitwarden/common/enums"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; + +export class DesktopLockComponentService implements LockComponentService { + private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); + private readonly platformUtilsService = inject(PlatformUtilsService); + private readonly biometricsService = inject(BiometricsService); + private readonly pinService = inject(PinServiceAbstraction); + + constructor() {} + + getBiometricsError(error: any): string | null { + return null; + } + + getPreviousUrl(): string | null { + return null; + } + + async isWindowVisible(): Promise { + return ipc.platform.isWindowVisible(); + } + + getBiometricsUnlockBtnText(): string { + switch (this.platformUtilsService.getDevice()) { + case DeviceType.MacOsDesktop: + return "unlockWithTouchId"; + case DeviceType.WindowsDesktop: + return "unlockWithWindowsHello"; + case DeviceType.LinuxDesktop: + return "unlockWithPolkit"; + default: + throw new Error("Unsupported platform"); + } + } + + getAvailableUnlockOptions$(userId: UserId): Observable { + return combineLatest([ + // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to + defer(() => this.biometricsService.getBiometricsStatusForUser(userId)), + this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), + defer(() => this.pinService.isPinDecryptionAvailable(userId)), + ]).pipe( + map(([biometricsStatus, userDecryptionOptions, pinDecryptionAvailable]) => { + const unlockOpts: UnlockOptions = { + masterPassword: { + enabled: userDecryptionOptions.hasMasterPassword, + }, + pin: { + enabled: pinDecryptionAvailable, + }, + biometrics: { + enabled: biometricsStatus == BiometricsStatus.Available, + biometricsStatus: biometricsStatus, + }, + }; + + return unlockOpts; + }), + ); + } +} diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index ffb6159a46f..b73542ca725 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -1,36 +1,58 @@ import { ipcRenderer } from "electron"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsStatus } from "@bitwarden/key-management"; import { BiometricMessage, BiometricAction } from "../types/biometric-message"; const biometric = { - enabled: (userId: string): Promise => + authenticateWithBiometrics: (): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.EnabledForUser, - key: `${userId}_user_biometric`, - keySuffix: KeySuffixOptions.Biometric, + action: BiometricAction.Authenticate, + } satisfies BiometricMessage), + getBiometricsStatus: (): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.GetStatus, + } satisfies BiometricMessage), + unlockWithBiometricsForUser: (userId: string): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.UnlockForUser, + userId: userId, + } satisfies BiometricMessage), + getBiometricsStatusForUser: (userId: string): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.GetStatusForUser, userId: userId, } satisfies BiometricMessage), - osSupported: (): Promise => + setBiometricProtectedUnlockKeyForUser: (userId: string, value: string): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.OsSupported, + action: BiometricAction.SetKeyForUser, + userId: userId, + key: value, } satisfies BiometricMessage), - biometricsNeedsSetup: (): Promise => + deleteBiometricUnlockKeyForUser: (userId: string): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.NeedsSetup, + action: BiometricAction.RemoveKeyForUser, + userId: userId, } satisfies BiometricMessage), - biometricsSetup: (): Promise => + setupBiometrics: (): Promise => ipcRenderer.invoke("biometric", { action: BiometricAction.Setup, } satisfies BiometricMessage), - biometricsCanAutoSetup: (): Promise => + setClientKeyHalf: (userId: string, value: string): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.CanAutoSetup, + action: BiometricAction.SetClientKeyHalf, + userId: userId, + key: value, } satisfies BiometricMessage), - authenticate: (): Promise => + getShouldAutoprompt: (): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.Authenticate, + action: BiometricAction.GetShouldAutoprompt, + } satisfies BiometricMessage), + setShouldAutoprompt: (should: boolean): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.SetShouldAutoprompt, + data: should, } satisfies BiometricMessage), }; diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 3b7701c5d67..5ee3ba7e029 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Beveiligde Nota" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Vouers" }, @@ -177,6 +180,63 @@ "address": { "message": "Adres" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premie word vereis" }, @@ -189,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januarie" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopieer Wagwoord" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Bedienerbronadres" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Wagwoordgeskiedenis" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Wis", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Daar is geen wagwoorde om te lys nie." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Ontdoen" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bevestig vir Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Ontgrendel met Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Gebruikersnaamtipe" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "'n Kennisgewing was na jou toestel gestuur." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Maak asseblief seker dat jou kluis oopgesluit is en die Vingerafdrukfrase met die ander toestel vergelyk." }, "fingerprintPhraseHeader": { "message": "Vingerafdrukfrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Aan teken met toestel moet opgestel word in die instellings van die Bitwarden toep. Nodig 'n ander opsie?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Beskou alle aantekenings opsies" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Swak wagwoord geidentifiseer en gevind in 'n data lekkasie. Gebruik 'n sterk en unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy hierdie wagwoord wil gebruik?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontroleer bekende data lekkasies vir hierdie wagwoord" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Belangrik:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Aanbevole bywerking van instellings" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Toestel goedkeuring word vereis. Kies 'n goedkeuings opsie hieronder:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Onthou hierdie toestel" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Gebruiker-e-pos is vermis" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Toestel is vertroud" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 6c4b52797b7..132021f7760 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "ملاحظة آمنة" }, + "typeSshKey": { + "message": "مفتاح SSH" + }, "folders": { "message": "مجلدات" }, @@ -33,7 +36,7 @@ "message": "مجموعات" }, "searchVault": { - "message": "البحث في الخزنة" + "message": "البحث في الخزانة" }, "addItem": { "message": "إضافة عنصر" @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "مرحبًا بعودتك" }, "moveToOrgDesc": { "message": "اختر المؤسسة التي ترغب في نقل هذا العنصر إليها. يؤدي الانتقال إلى مؤسسة إلى نقل ملكية العنصر إلى تلك المؤسسة. لن تكون المالك المباشر لهذا العنصر بعد نقله." @@ -177,6 +180,63 @@ "address": { "message": "العنوان" }, + "sshPrivateKey": { + "message": "مفتاح خاص" + }, + "sshPublicKey": { + "message": "مفتاح عام" + }, + "sshFingerprint": { + "message": "بصمة الأصبع" + }, + "sshKeyAlgorithm": { + "message": "نوع المفتاح" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "أُنشئ مفتاح SSH جديد" + }, + "sshKeyWrongPassword": { + "message": "كلمة المرور التي أدخلتها غير صحيحة." + }, + "importSshKey": { + "message": "استيراد" + }, + "confirmSshKeyPassword": { + "message": "تأكيد كلمة المرور" + }, + "enterSshKeyPasswordDesc": { + "message": "أدخل كلمة المرور لمفتاح SSH." + }, + "enterSshKeyPassword": { + "message": "أدخل كلمة المرور" + }, + "sshAgentUnlockRequired": { + "message": "الرجاء فتح المخزن الخاص بك للموافقة على طلب مفتاح SSH." + }, + "sshAgentUnlockTimeout": { + "message": "انتهت مهلة طلب مفتاح SSH." + }, + "enableSshAgent": { + "message": "تمكين وكيل SSH" + }, + "enableSshAgentDesc": { + "message": "تمكين وكيل SSH لتوقيع طلبات SSH مباشرة من خزانة Bitwarden الخاص بك." + }, + "enableSshAgentHelp": { + "message": "وكيل SSH هو خدمة موجهة للمطورين تسمح لك بتوقيع طلبات SSH مباشرة من مخزن Bitwarden الخاص بك." + }, "premiumRequired": { "message": "مطلوب اشتراك بريميوم" }, @@ -189,6 +249,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "يناير" }, @@ -263,7 +337,7 @@ "message": "توليد كلمة المرور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "توليد عبارة المرور" }, "type": { "message": "نوع" @@ -400,8 +474,14 @@ "copyPassword": { "message": "نسخ كلمة المرور" }, + "regenerateSshKey": { + "message": "تجديد مفتاح SSH" + }, + "copySshPrivateKey": { + "message": "نسخ المفتاح الخاص SSH" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "نسخ عبارة المرور", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -432,11 +512,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": { @@ -444,7 +524,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": { @@ -452,7 +532,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": { @@ -460,7 +540,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" }, "specialCharactersLabel": { @@ -495,11 +575,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "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." }, "searchCollection": { @@ -552,13 +632,13 @@ "message": "تم حذف المجلد" }, "loginOrCreateNewAccount": { - "message": "قم بالتسجيل أو إنشاء حساب جديد للوصول إلى خزنتك الآمنة." + "message": "قم بالتسجيل أو إنشاء حساب جديد للوصول إلى خزانتك الآمنة." }, "createAccount": { "message": "إنشاء حساب" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "جديد على Bitwarden؟" }, "setAStrongPassword": { "message": "تعيين كلمة مرور قوية" @@ -570,16 +650,16 @@ "message": "تسجيل الدخول" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "تسجيل الدخول إلى Bitwarden" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "تسجيل الدخول باستخدام مفتاح المرور" }, "loginWithDevice": { - "message": "Log in with device" + "message": "تسجيل الدخول بالجهاز" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استخدام تسجيل دخول واحد" }, "submit": { "message": "إرسال" @@ -628,7 +708,7 @@ "message": "الانضمام إلى المؤسسة" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "انضم إلى $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -643,16 +723,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": "أدخل عنوان البريد الإلكتروني لحسابك وسيُرسل تلميح كلمة المرور الخاصة بك إليك" }, "passwordHint": { "message": "تلميح كلمة المرور" @@ -698,10 +778,10 @@ "message": "تم إنشاء حسابك الجديد! يمكنك الآن تسجيل الدخول." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "أنشئ حسابك الجديد!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "لقد قمت بتسجيل الدخول!" }, "masterPassSent": { "message": "لقد أرسلنا لك رسالة بريد إلكتروني تحتوي على تلميحات كلمة المرور الرئيسية." @@ -853,8 +933,14 @@ "baseUrl": { "message": "رابط الخادم" }, + "authenticationTimeout": { + "message": "مهلة المصادقة" + }, + "authenticationSessionTimedOut": { + "message": "انتهت مهلة جَلسة المصادقة. يرجى إعادة بَدْء عملية تسجيل الدخول." + }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "رابط خادم الاستضافة الذاتية", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -939,7 +1025,7 @@ "message": "تحميل..." }, "lockVault": { - "message": "قفل الخزنة" + "message": "قفل الخزانة" }, "passwordGenerator": { "message": "مولّد كلمة المرور" @@ -963,7 +1049,7 @@ "message": "تابعنا" }, "syncVault": { - "message": "مزامنة الخزنة" + "message": "مزامنة الخزانة" }, "changeMasterPass": { "message": "تغيير كلمة المرور الرئيسية" @@ -983,7 +1069,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { - "message": "اذهب لخزنة الوب" + "message": "اذهب لخزانة الوب" }, "getMobileApp": { "message": "احصل على تطبيق الهاتف المحمول" @@ -998,19 +1084,19 @@ "message": "فشلت المزامنة" }, "yourVaultIsLocked": { - "message": "خزنتك مقفلة. تحقق من هويتك للمتابعة." + "message": "خزانتك مقفلة. تحقق من هويتك للمتابعة." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حسابك مقفل" }, "or": { - "message": "or" + "message": "أو" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "فتح باستخدام القياسات الحيوية" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "فتح بكلمة مرور رئيسية" }, "unlock": { "message": "فتح" @@ -1032,19 +1118,19 @@ "message": "كلمة المرور الرئيسية غير صالحة" }, "twoStepLoginConfirmation": { - "message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزنة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟" + "message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزانة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟" }, "twoStepLogin": { "message": "تسجيل الدخول بخطوتين" }, "vaultTimeout": { - "message": "مهلة الخزنة" + "message": "مهلة الخزانة" }, "vaultTimeout1": { - "message": "Timeout" + "message": "المهلة" }, "vaultTimeoutDesc": { - "message": "اختر وقت انتهاء مهلة خزنتك وقم بتنفيذ الإجراء المحدد." + "message": "اختر وقت انتهاء مهلة خزانتك وقم بتنفيذ الإجراء المحدد." }, "immediately": { "message": "فورًا" @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "نسخ البريد الإلكتروني" }, "copySecurityCode": { "message": "نسخ رمز الأمان", @@ -1279,10 +1365,10 @@ "message": "خيارات تسجيل الدخول بخطوتين المملوكة مثل YubiKey و Duo." }, "premiumSignUpReports": { - "message": "نظافة كلمة المرور، صحة الحساب، وتقارير خرق البيانات للحفاظ على سلامة خزنتك." + "message": "نظافة كلمة المرور، صحة الحساب، وتقارير خرق البيانات للحفاظ على سلامة خزانتك." }, "premiumSignUpTotp": { - "message": "مورد رمز التحقق (2FA) لتسجيل الدخول في خزنتك." + "message": "مورد رمز التحقق (2FA) لتسجيل الدخول في خزانتك." }, "premiumSignUpSupport": { "message": "أولوية دعم العملاء." @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "سجل كلمة المرور" }, + "generatorHistory": { + "message": "سجل المولد" + }, + "clearGeneratorHistoryTitle": { + "message": "محو سجل المولد" + }, + "cleargGeneratorHistoryDescription": { + "message": "إذا تابعت، سيتم حذف جميع الإدخالات بشكل دائم من سجل المولد. هل أنت متيقِّن من أنك تريد المتابعة؟" + }, "clear": { "message": "مسح", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "لا توجد كلمات مرور للعرض." }, + "clearHistory": { + "message": "مسح السجل" + }, + "nothingToShow": { + "message": "لا يوجد شيء لعرضه" + }, + "nothingGeneratedRecently": { + "message": "لم تقم بتوليد أي شيء مؤخرًا" + }, "undo": { "message": "تراجع" }, @@ -1402,7 +1506,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "تم النسخ بنجاح" }, "errorRefreshingAccessToken": { "message": "خطأ في تحديث رمز الوصول" @@ -1501,7 +1605,7 @@ "message": "تصدير من" }, "exportVault": { - "message": "تصدير الخزنة" + "message": "تصدير الخزانة" }, "fileFormat": { "message": "صيغة الملف" @@ -1572,10 +1676,10 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "تأكيد تصدير الخزنة" + "message": "تأكيد تصدير الخزانة" }, "exportWarningDesc": { - "message": "يحتوي هذا التصدير على بيانات خزنتك بتنسيق غير مشفر. لا يجب عليك تخزين أو إرسال الملف الذي تم تصديره عبر قنوات غير آمنة (مثل البريد الإلكتروني). احذفه مباشرة بعد انتهائك من استخدامه." + "message": "يحتوي هذا التصدير على بيانات خزانتك بتنسيق غير مشفر. لا يجب عليك تخزين أو إرسال الملف الذي تم تصديره عبر قنوات غير آمنة (مثل البريد الإلكتروني). احذفه مباشرة بعد انتهائك من استخدامه." }, "encExportKeyWarningDesc": { "message": "يقوم هذا التصدير بتشفير بياناتك باستخدام مفتاح تشفير حسابك. إذا قمت بتدوير مفتاح تشفير حسابك يجب عليك التصدير مرة أخرى لأنك لن تتمكن من فك تشفير ملف التصدير هذا." @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "تحقق من Bitwarden." }, - "polkitConsentMessage": { - "message": "مصادقة لفتح Bitwarden." - }, "unlockWithTouchId": { "message": "فتح بواسطة معرف اللمس" }, @@ -1654,7 +1755,7 @@ "message": "إعدادات معرف اللمس الإضافية" }, "touchIdConsentMessage": { - "message": "فتح خزنتك" + "message": "فتح خزانتك" }, "autoPromptWindowsHello": { "message": "اسأل عن Windows Hello عند التشغيل" @@ -1672,22 +1773,22 @@ "message": "موصى به للأمان." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "قفل مع كلمة المرور الرئيسية عند إعادة تشغيل" }, "deleteAccount": { "message": "حذف الحساب" }, "deleteAccountDesc": { - "message": "تابع أدناه لحذف حسابك وجميع بيانات خزنتك." + "message": "تابع أدناه لحذف حسابك وجميع بيانات خزانتك." }, "deleteAccountWarning": { "message": "حذف حسابك دائم. لا يمكن التراجع عنه." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "لا يمكن حذف الحساب" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "لا يمكن إكمال هذا الإجراء لأن حسابك مملوك من قبل منظمة. اتصل بمسؤول مؤسستك للحصول على تفاصيل إضافية." }, "accountDeleted": { "message": "تم حذف الحساب" @@ -1742,13 +1843,13 @@ "message": "واحدة أو أكثر من سياسات المؤسسة تؤثر على إعدادات المولدات الخاصة بك." }, "vaultTimeoutAction": { - "message": "إجراء مهلة الخزنة" + "message": "إجراء مهلة الخزانة" }, "vaultTimeoutActionLockDesc": { - "message": "الخزنة المقفلة تتطلب إعادة إدخال كلمة المرور الرئيسية الخاصة بك للوصول إليها مرة أخرى." + "message": "الخزانة المقفلة تتطلب إعادة إدخال كلمة المرور الرئيسية الخاصة بك للوصول إليها مرة أخرى." }, "vaultTimeoutActionLogOutDesc": { - "message": "تسجيل الخروج من الخزنة يتطلب إعادة المصادقة للوصول إليها مرة أخرى." + "message": "تسجيل الخروج من الخزانة يتطلب إعادة المصادقة للوصول إليها مرة أخرى." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "أعدنّ طريقة إلغاء القُفْل لتغيير إجراء مهلة المخزن الخاص بك." @@ -1780,7 +1881,7 @@ "message": "حُذف نهائيًا" }, "vaultTimeoutLogOutConfirmation": { - "message": "سيؤدي تسجيل الخروج إلى إزالة جميع إمكانية الوصول إلى خزنتك ويتطلب المصادقة عبر الإنترنت بعد انتهاء المهلة. هل أنت متأكد من أنك تريد استخدام هذا الإعداد؟" + "message": "سيؤدي تسجيل الخروج إلى إزالة جميع إمكانية الوصول إلى خزانتك ويتطلب المصادقة عبر الإنترنت بعد انتهاء المهلة. هل أنت متأكد من أنك تريد استخدام هذا الإعداد؟" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "تأكيد إجراء المهلة" @@ -1800,7 +1901,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "من $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1888,7 +1989,7 @@ "message": "السماح بتكامل المتصفح" }, "enableBrowserIntegrationDesc1": { - "message": "Used to allow biometric unlock in browsers that are not Safari." + "message": "تستخدم للسماح بفتح القفل الحيوي في المتصفحات غير Safari." }, "enableDuckDuckGoBrowserIntegration": { "message": "السماح بدمج متصفح DuckDuckGo" @@ -1993,7 +2094,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "خزنتي" + "message": "خزانتي" }, "text": { "message": "نص" @@ -2171,7 +2272,7 @@ "message": "تم تغيير كلمة المرور الرئيسية الخاصة بك مؤخرًا من قبل مسؤول في مؤسستك. من أجل الوصول إلى الخزانة، يجب عليك تحديثها الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." }, "updateWeakMasterPasswordWarning": { - "message": "كلمة المرور الرئيسية الخاصة بك لا تفي بواحدة أو أكثر من سياسات مؤسستك. من أجل الوصول إلى الخزنة، يجب عليك تحديث كلمة المرور الرئيسية الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." + "message": "كلمة المرور الرئيسية الخاصة بك لا تفي بواحدة أو أكثر من سياسات مؤسستك. من أجل الوصول إلى الخزانة، يجب عليك تحديث كلمة المرور الرئيسية الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." }, "tdeDisabledMasterPasswordRequired": { "message": "لقد قامت مؤسستك بتعطيل تشفير الجهاز الموثوق به. الرجاء تعيين كلمة مرور رئيسية للوصول إلى خزانك." @@ -2219,7 +2320,7 @@ "message": "دقائق" }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ ساعة و $MINUTES$ دقيقة كحد أقصى.", "placeholders": { "hours": { "content": "$1", @@ -2232,7 +2333,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "سياسات مؤسستك تؤثر على مهلة خزنتك. الحد الأقصى المسموح به لمهلة الخزنة هو $HOURS$ ساعة(ساعات) و $MINUTES$ دقيقة(دقائق). يتم تعيين إجراء مهلة المخزن الخاص بك إلى $ACTION$.", + "message": "سياسات مؤسستك تؤثر على مهلة خزانتك. الحد الأقصى المسموح به لمهلة الخزانة هو $HOURS$ ساعة(ساعات) و $MINUTES$ دقيقة(دقائق). يتم تعيين إجراء مهلة المخزن الخاص بك إلى $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2249,7 +2350,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "لقد حددت سياسات مؤسستك إجراء مهلة الخزنة الخاصة بك إلى $ACTION$.", + "message": "لقد حددت سياسات مؤسستك إجراء مهلة الخزانة الخاصة بك إلى $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2258,7 +2359,7 @@ } }, "vaultTimeoutTooLarge": { - "message": "مهلة خزنتك تتجاوز القيود التي تضعها مؤسستك." + "message": "مهلة خزانتك تتجاوز القيود التي تضعها مؤسستك." }, "inviteAccepted": { "message": "تم قبول الدعوة" @@ -2270,7 +2371,7 @@ "message": "هذه المؤسسة لديها سياسة الشركة التي ستقوم تلقائياً بتسجيلك في إعادة تعيين كلمة المرور. التسجيل سيسمح لمسؤولي المؤسسة بتغيير كلمة المرور الرئيسية الخاصة بك." }, "vaultExportDisabled": { - "message": "تصدير الخزنة معطل" + "message": "تصدير الخزانة معطل" }, "personalVaultExportPolicyInEffect": { "message": "واحدة أو أكثر من سياسات المؤسسة تمنعك من تصدير خزانتك الشخصية." @@ -2306,7 +2407,7 @@ "message": "خطأ في موصل المفتاح: تأكد من أن موصل المفتاح متاح ويعمل بشكل صحيح." }, "lockAllVaults": { - "message": "قفل جميع الخزنات" + "message": "قفل جميع الخزانات" }, "accountLimitReached": { "message": "لا يمكن تسجيل دخول أكثر من 5 حسابات في نفس الوقت." @@ -2342,7 +2443,7 @@ "message": "انتهت مهلة جلستك. يرجى العودة ومحاولة تسجيل الدخول مرة أخرى." }, "exportingPersonalVaultTitle": { - "message": "تصدير الخزنة الشخصية" + "message": "تصدير الخزانة الشخصية" }, "exportingIndividualVaultDescription": { "message": "سيتم تصدير عناصر المخزن الفردية المرتبطة بـ $EMAIL$ فقط. لن يتم إدراج عناصر مخزن المؤسسة. سيتم تصدير معلومات المنتج فقط ولن تشمل المرفقات المرتبطة بها.", @@ -2354,7 +2455,7 @@ } }, "exportingOrganizationVaultTitle": { - "message": "تصدير خزنة المؤسسة" + "message": "تصدير خزانة المؤسسة" }, "exportingOrganizationVaultDesc": { "message": "فقط مستودع المؤسسة المرتبط بـ $ORGANIZATION$ سيتم تصديره. لن يتم إدراج العناصر في المستودعات الفردية أو المنظمات الأخرى.", @@ -2369,7 +2470,7 @@ "message": "مقفل" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "المخزن الخاص بك مقفل" }, "unlocked": { "message": "غير مقفل" @@ -2391,10 +2492,10 @@ "message": "إنشاء اسم المستخدم" }, "generateEmail": { - "message": "Generate email" + "message": "إنشاء بريد إلكتروني" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "يجب أن تكون القيمة بين $MIN$ و $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "نوع اسم المستخدم" }, @@ -2436,7 +2557,7 @@ "message": "الخدمة" }, "allVaults": { - "message": "جميع الخزنات" + "message": "جميع الخزانات" }, "searchOrganization": { "message": "البحث عن مؤسسة" @@ -2451,11 +2572,11 @@ "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": { @@ -2583,7 +2704,7 @@ "message": "هل أنت متأكد من أنك تريد استخدام خيار \"مطلقا\"؟ إعداد خيارات القفل إلى \"مطلقا\" يخزن مفتاح تشفير المستودع الخاص بك على جهازك. إذا كنت تستخدم هذا الخيار، يجب أن تتأكد من الحفاظ على حماية جهازك بشكل صحيح." }, "vault": { - "message": "الخزنة" + "message": "الخزانة" }, "loginWithMasterPassword": { "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "تم إرسال إشعار إلى جهازك." }, + "aNotificationWasSentToYourDevice": { + "message": "أُرسل إشعار إلى جهازك" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "تيقن من أن حسابك مفتوح وأن عبارة البصمة متطابقة على الجهاز الآخر" + }, + "needAnotherOptionV1": { + "message": "هل تحتاج إلى خيار آخر؟" + }, "fingerprintMatchInfo": { "message": "الرجاء التأكد من أن المخزن الخاص بك غير مقفل وأن عبارة بصمة الإصبع تطابق الجهاز الآخر." }, "fingerprintPhraseHeader": { "message": "عبارة بصمة الإصبع" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "سيتم إعلامك بمجرد الموافقة على الطلب" + }, "needAnotherOption": { "message": "تسجيل الدخول باستخدام الجهاز يجب أن يتم إعداده في إعدادات تطبيق Bitwarden. هل تحتاج إلى خيار آخر؟" }, + "viewAllLogInOptions": { + "message": "عرض جميع خيارات تسجيل الدخول" + }, "viewAllLoginOptions": { "message": "عرض جميع خيارات تسجيل الدخول" }, @@ -2743,14 +2879,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "كلمة مرور ضعيفة محددة وموجودة في خرق البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "تحقق من خروقات البيانات المعروفة لكلمة المرور هذه" }, + "loggedInExclamation": { + "message": "سجلت دخولك!" + }, "important": { "message": "مهم:" }, "accessing": { - "message": "Accessing" + "message": "الوصول" }, "accessTokenUnableToBeDecrypted": { "message": "لقد قمت بتسجيل الخروج لأنه لا يمكن فك تشفير الرمز المميز الخاص بك. الرجاء تسجيل الدخول مرة أخرى لحل هذه المشكلة." @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "تحديث الإعدادات الموصى بها" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "تذكر هذا الجهاز لجعل تسجيلات الدخول في المستقبل سلسة" + }, "deviceApprovalRequired": { "message": "موافقة الجهاز مطلوبة. حدد خيار الموافقة أدناه:" }, + "deviceApprovalRequiredV2": { + "message": "موافقة الجهاز مطلوبة" + }, + "selectAnApprovalOptionBelow": { + "message": "حدد خِيار الموافقة أدناه" + }, "rememberThisDevice": { "message": "تذكر هذا الجهاز" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "البريد الإلكتروني للمستخدم مفقود" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "لم يتم العثور على البريد الإلكتروني للمستخدم الحالي. يتم تسجيل الخروج." + }, "deviceTrusted": { "message": "الجهاز موثوق به" }, @@ -3067,7 +3224,7 @@ } }, "confirmVaultImport": { - "message": "تأكيد تصدير الخزنة" + "message": "تأكيد تصدير الخزانة" }, "confirmVaultImportDesc": { "message": "هذا الملف محمي بكلمة مرور. الرجاء إدخال كلمة مرور الملف لاستيراد البيانات." @@ -3223,9 +3380,99 @@ "message": "إرسال النص" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "لم يُعثر على أي منفذ مجاني لتسجيل الدخول." + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "السماح" + }, + "deny": { + "message": "رفض" + }, + "sshkeyApprovalTitle": { + "message": "تأكيد استخدام مفتاح SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "يطلب الوصول إلى" + }, + "unknownApplication": { + "message": "تطبيق" + }, + "sshKeyPasswordUnsupported": { + "message": "استيراد مفاتيح SSH المحمية بكلمة المرور غير معتمد حتى الآن" + }, + "invalidSshKey": { + "message": "مفتاح SSH غير صالح" + }, + "sshKeyTypeUnsupported": { + "message": "نوع مفتاح SSH غير معتمد" + }, + "importSshKeyFromClipboard": { + "message": "استيراد المفتاح من الحافظة" + }, + "sshKeyPasted": { + "message": "تم استيراد مفتاح SSH بنجاح" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "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": "تغيير البريد الإلكتروني الخاص بالحساب" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 5a5bac19bc1..9af089d1ef9 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Güvənli qeyd" }, + "typeSshKey": { + "message": "SSH açarı" + }, "folders": { "message": "Qovluqlar" }, @@ -177,6 +180,63 @@ "address": { "message": "Ünvan" }, + "sshPrivateKey": { + "message": "Private açar" + }, + "sshPublicKey": { + "message": "Public açar" + }, + "sshFingerprint": { + "message": "Barmaq izi" + }, + "sshKeyAlgorithm": { + "message": "Açar növü" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Yeni bir SSH açarı yaradıldı" + }, + "sshKeyWrongPassword": { + "message": "Daxil etdiyiniz parol yanlışdır." + }, + "importSshKey": { + "message": "Daxilə köçür" + }, + "confirmSshKeyPassword": { + "message": "Parolu təsdiqlə" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH açarı üçün parolu daxil edin." + }, + "enterSshKeyPassword": { + "message": "Parolu daxil edin" + }, + "sshAgentUnlockRequired": { + "message": "SSH açar tələbini təsdiqləmək üçün seyfinizin kilidini açın." + }, + "sshAgentUnlockTimeout": { + "message": "SSH açar tələbinin vaxtı bitdi." + }, + "enableSshAgent": { + "message": "SSH agenti fəallaşdır" + }, + "enableSshAgentDesc": { + "message": "SSH tələblərini birbaşa Bitwarden seyfinizdən imzalamaq üçün SSH agentini fəallaşdırın." + }, + "enableSshAgentHelp": { + "message": "SSH agenti, SSH tələblərini birbaşa Bitwarden seyfinizdən imzalamağa imkan verən developerlərə yönəlmiş bir xidmətdir." + }, "premiumRequired": { "message": "Premium üzvlük lazımdır" }, @@ -189,6 +249,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Yanvar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Parolu kopyala" }, + "regenerateSshKey": { + "message": "SSH açarını yenidən yarat" + }, + "copySshPrivateKey": { + "message": "SSH private açarını kopyala" + }, "copyPassphrase": { "message": "Keçid ifadəsini kopyala", "description": "Copy passphrase to clipboard" @@ -579,7 +659,7 @@ "message": "Cihazla giriş et" }, "useSingleSignOn": { - "message": "Tək daxil olma üsulunu istifadə et" + "message": "Vahid daxil olma üsulunu istifadə et" }, "submit": { "message": "Göndər" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL-si" }, + "authenticationTimeout": { + "message": "Kimlik doğrulama vaxtı bitdi" + }, + "authenticationSessionTimedOut": { + "message": "Kimlik doğrulama seansının vaxtı bitdi. Lütfən giriş prosesini yenidən başladın." + }, "selfHostBaseUrl": { "message": "Self-host server URL-si", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Parol tarixçəsi" }, + "generatorHistory": { + "message": "Yaradıcı tarixçəsi" + }, + "clearGeneratorHistoryTitle": { + "message": "Yaradıcı tarixçəsini təmizlə" + }, + "cleargGeneratorHistoryDescription": { + "message": "Davam etsəniz, bütün girişlər yaradıcı tarixçəsindən həmişəlik silinəcək. Davam etmək istəyirsiniz?" + }, "clear": { "message": "Təmizlə", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Siyahılanacaq heç bir parol yoxdur." }, + "clearHistory": { + "message": "Tarixçəni təmizlə" + }, + "nothingToShow": { + "message": "Göstəriləcək heç nə yoxdur" + }, + "nothingGeneratedRecently": { + "message": "Təzəlikcə heç nə yaratmamısınız" + }, "undo": { "message": "Geri al" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bitwarden üçün doğrula." }, - "polkitConsentMessage": { - "message": "Bitwarden kilidini açmaq üçün kimliyi doğrula." - }, "unlockWithTouchId": { "message": "Touch ID kilidini aç" }, @@ -1687,7 +1788,7 @@ "message": "Hesab silinə bilmir" }, "cannotDeleteAccountDesc": { - "message": "Hesabınız bir təşkilata aid olduğu üçün bu əməliyyat icra edilə bilməz. Əlavə məlumat üçün təşkilatınızın administratoru ilə əlaqə saxlayın." + "message": "Hesabınız bir təşkilata aid olduğu üçün bu əməliyyat icra edilə bilməz. Əlavə məlumat üçün təşkilatınızın inzibatçısı ilə əlaqə saxlayın." }, "accountDeleted": { "message": "Hesab silindi" @@ -1786,7 +1887,7 @@ "message": "Vaxt bitmə əməliyyat təsdiqi" }, "enterpriseSingleSignOn": { - "message": "Müəssisə üçün tək daxil olma" + "message": "Müəssisə üçün vahid daxil olma" }, "setMasterPassword": { "message": "Ana parolu ayarla" @@ -2168,7 +2269,7 @@ "message": "Ana parolu güncəllə" }, "updateMasterPasswordWarning": { - "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir administrator tərəfindən dəyişdirildi. Seyfə müraciət etmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir inzibatçı tərəfindən dəyişdirildi. Seyfə müraciət etmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, "updateWeakMasterPasswordWarning": { "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə müraciət üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." @@ -2267,7 +2368,7 @@ "message": "Avtomatik qeydiyyat" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Bu təşkilat, sizi \"parol sıfırlama\"da avtomatik olaraq qeydiyyata alan müəssisə siyasətinə sahibdir. Qeydiyyat, təşkilat administratorlarına ana parolunuzu dəyişdirmə icazəsi verəcək." + "message": "Bu təşkilat, sizi \"parol sıfırlama\"da avtomatik olaraq qeydiyyata alan müəssisə siyasətinə sahibdir. Qeydiyyat, təşkilat inzibatçılarına ana parolunuzu dəyişdirmə icazəsi verəcək." }, "vaultExportDisabled": { "message": "Seyf xaricə köçürmə silindi" @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "E-poçt yarat" }, - "generatorBoundariesHint": { - "message": "Dəyər $MIN$-$MAX$ arasında olmalıdır", + "spinboxBoundariesHint": { + "message": "Dəyər, $MIN$-$MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Güclü bir parol yaratmaq üçün $RECOMMENDED$ və ya daha çox xarakter istifadə edin.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Güclü bir keçid ifadəsi yaratmaq üçün $RECOMMENDED$ və ya daha çox söz istifadə edin.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "İstifadəçi adı növü" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Cihazınıza bir bildiriş göndərildi." }, + "aNotificationWasSentToYourDevice": { + "message": "Cihazınıza bir bildiriş göndərildi" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lütfən hesabınızın kilidinin açıq olduğuna və barmaq izi ifadəsinin digər cihazla uyuşduğuna əmin olun" + }, + "needAnotherOptionV1": { + "message": "Başqa bir seçimə ehtiyacınız var?" + }, "fingerprintMatchInfo": { "message": "Lütfən seyfinizin kilidinin açıq olduğuna və Barmaq izi ifadəsinin digər cihazla uyuşduğuna əmin olun." }, "fingerprintPhraseHeader": { "message": "Barmaq izi ifadəsi" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Tələbiniz təsdiqləndikdə məlumatlandırılacaqsınız" + }, "needAnotherOption": { "message": "Cihazla giriş etmə, Bitwarden tətbiqinin ayarlarında qurulmalıdır. Başqa bir seçimə ehtiyacınız var?" }, + "viewAllLogInOptions": { + "message": "Bütün giriş seçimlərinə bax" + }, "viewAllLoginOptions": { "message": "Bütün giriş seçimlərinə bax" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zəif parol məlumat pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" }, + "useThisPassword": { + "message": "Bu parolu istifadə et" + }, + "useThisUsername": { + "message": "Bu istifadəçi adını istifadə et" + }, "checkForBreaches": { "message": "Bu parol üçün bilinən məlumat pozuntularını yoxlayın" }, + "loggedInExclamation": { + "message": "Giriş edildi!" + }, "important": { "message": "Vacib:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Tövsiyə edilən Ayarlar Güncəlləməsi" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Gələcəkdəki girişləri problemsiz etmək üçün bu cihazı xatırla" + }, "deviceApprovalRequired": { "message": "Cihaz təsdiqi tələb olunur. Aşağıdan bir təsdiq variantı seçin:" }, + "deviceApprovalRequiredV2": { + "message": "Cihazın təsdiq olunması tələb olunur" + }, + "selectAnApprovalOptionBelow": { + "message": "Aşağıdan bir təsdiq seçimi edin" + }, "rememberThisDevice": { "message": "Bu cihazı xatırla" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "İstifadəçi e-poçtu əskikdir" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiv istifadəçi e-poçtu tapılmadı. Hesabınızdan çıxış edilir." + }, "deviceTrusted": { "message": "Cihaz güvənlidir" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "SSO giriş üçün açıq port tapıla bilmədi." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, + "authorize": { + "message": "Səlahiyyət ver" + }, + "deny": { + "message": "Rədd et" + }, + "sshkeyApprovalTitle": { + "message": "SSH açar istifadəsini təsdiqlə" + }, + "sshkeyApprovalMessageInfix": { + "message": "bura müraciət tələb edir:" + }, + "unknownApplication": { + "message": "Bir tətbiq" + }, + "sshKeyPasswordUnsupported": { + "message": "Parolla qorunan SSH açarlarının daxilə köçürülməsi hələ dəstəklənmir" + }, + "invalidSshKey": { + "message": "SSH açarı yararsızdır" + }, + "sshKeyTypeUnsupported": { + "message": "SSH açar növü dəstəklənmir" + }, + "importSshKeyFromClipboard": { + "message": "Açarı lövhədən daxilə köçür" + }, + "sshKeyPasted": { + "message": "SSH açarı uğurla daxilə köçürüldü" + }, "fileSavedToDevice": { "message": "Fayl cihazda saxlanıldı. Endirilənləri cihazınızdan idarə edin." + }, + "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" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 3ed643aec14..e778c59525f 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Бяспечныя нататкі" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Папкі" }, @@ -177,6 +180,63 @@ "address": { "message": "Адрас" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Патрабуецца прэміяльны статус" }, @@ -189,6 +249,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Студзень" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Скапіяваць пароль" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL-адрас сервера" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Гісторыя пароляў" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Ачысціць", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "У спісе адсутнічаюць паролі." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Адрабіць" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Праверыць на Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Разблакіраваць з Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тып імя карыстальніка" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Апавяшчэнне было адпраўлена на вашу прыладу." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Пераканайцеся, што ваша сховішча разблакіравана, а фраза адбітка пальца супадае з іншай прыладай." }, "fingerprintPhraseHeader": { "message": "Фраза адбітка пальца" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Неабходна наладзіць уваход з прыладай у наладах мабільнай праграмы Bitwarden. Патрабуецца іншы варыянт?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Паглядзець усе параметры ўваходу" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Вызначаны ненадзейны пароль, які знойдзены ва ўцечках даных. Выкарыстоўвайце надзейныя і ўнікальныя паролі для абароны свайго ўліковага запісу. Вы сапраўды хочаце выкарыстоўваць гэты пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Праверыць у вядомых уцечках даных для гэтага пароля" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Важна:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Рэкамендаваныя налады абнаўлення" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Патрабуецца ўхваленне прылады. Выберыце параметры ўхвалення ніжэй:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Запомніць гэту прыладу" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Адсутнічае электронная пошта карыстальніка" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Давераная прылада" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index b60ac35472d..9b000177bbe 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Защитена бележка" }, + "typeSshKey": { + "message": "SSH ключ" + }, "folders": { "message": "Папки" }, @@ -177,6 +180,63 @@ "address": { "message": "Адрес" }, + "sshPrivateKey": { + "message": "Частен ключ" + }, + "sshPublicKey": { + "message": "Публичен ключ" + }, + "sshFingerprint": { + "message": "Отпечатък" + }, + "sshKeyAlgorithm": { + "message": "Тип на ключа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 бита" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 бита" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 бита" + }, + "sshKeyGenerated": { + "message": "Генериран е нов SSH ключ" + }, + "sshKeyWrongPassword": { + "message": "Въведената парола е неправилна." + }, + "importSshKey": { + "message": "Внасяне" + }, + "confirmSshKeyPassword": { + "message": "Потвърждаване на паролата" + }, + "enterSshKeyPasswordDesc": { + "message": "Въведете паролата за SSH-ключа." + }, + "enterSshKeyPassword": { + "message": "Въведете паролата" + }, + "sshAgentUnlockRequired": { + "message": "Отключете трезора си, за да одобрите заявката за SSH ключ." + }, + "sshAgentUnlockTimeout": { + "message": "Времето на заявката за SSH ключ е изтекло." + }, + "enableSshAgent": { + "message": "Включване на SSH-агента" + }, + "enableSshAgentDesc": { + "message": "Включете SSH-агента, за да подписвате заявки за SSH направо от трезора си в Битуорден." + }, + "enableSshAgentHelp": { + "message": "SSH-агентът е услуга, предназначена за разработчици, чрез която можете да подписвате заявки за SSH направо от трезора си в Битуорден." + }, "premiumRequired": { "message": "Изисква се платен абонамент" }, @@ -189,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "януари" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Копиране на паролата" }, + "regenerateSshKey": { + "message": "Генериране на нов SSH ключ" + }, + "copySshPrivateKey": { + "message": "Копиране на частния SSH ключ" + }, "copyPassphrase": { "message": "Копиране на паролата-фраза", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Адрес на сървъра" }, + "authenticationTimeout": { + "message": "Време на давност за удостоверяването" + }, + "authenticationSessionTimedOut": { + "message": "Сесията за удостоверяване е изтекла. Моля, започнете отначало процеса по вписване." + }, "selfHostBaseUrl": { "message": "Адрес на собствения сървър", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Хронология на паролата" }, + "generatorHistory": { + "message": "История на генерирането" + }, + "clearGeneratorHistoryTitle": { + "message": "Изчистване на историята на генериране" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ако продължите, всички записи в историята на генериране ще бъдат изтрити завинаги. Наистина ли искате това?" + }, "clear": { "message": "Изчистване", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Няма пароли за показване." }, + "clearHistory": { + "message": "Изчистване на историята" + }, + "nothingToShow": { + "message": "Няма нищо за показване" + }, + "nothingGeneratedRecently": { + "message": "Скоро не сте генерирали нищо" + }, "undo": { "message": "Отмяна" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Потвърждаване за Битуорден." }, - "polkitConsentMessage": { - "message": "Идентифицирайте се, за да отключите Битуорден." - }, "unlockWithTouchId": { "message": "Отключване с Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Генериране на електронна поща" }, - "generatorBoundariesHint": { - "message": "Стойността трябва да бъде между $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Стойността трябва да бъде между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип потребителско име" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Към устройството Ви е изпратено известие." }, + "aNotificationWasSentToYourDevice": { + "message": "Към устройството Ви е изпратено известие" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Уверете се, че регистрацията Ви е отключена и че уникалната фраза съвпада с другото устройство" + }, + "needAnotherOptionV1": { + "message": "Предпочитате друг вариант?" + }, "fingerprintMatchInfo": { "message": "Уверете се, че трезорът Ви е отключен и че Уникалната фраза съвпада с другото устройство." }, "fingerprintPhraseHeader": { "message": "Уникална фраза" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Ще получите уведомление когато заявката бъде одобрена" + }, "needAnotherOption": { "message": "Вписването с устройство трябва да е включено в настройките на приложението на Битуорден. Друга настройка ли търсите?" }, + "viewAllLogInOptions": { + "message": "Вижте всички възможности за вписване" + }, "viewAllLoginOptions": { "message": "Вижте всички възможности за вписване" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Разпозната е слаба парола. Използвайте силна парола, за да защитете данните си. Наистина ли искате да използвате слаба парола?" }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, "checkForBreaches": { "message": "Проверяване в известните случаи на изтекли данни за тази парола" }, + "loggedInExclamation": { + "message": "Вписахте се!" + }, "important": { "message": "Важно:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Препоръчителна промяна на настройките" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запомняне на това устройство, така че в бъдеще вписването да бъде по-лесно" + }, "deviceApprovalRequired": { "message": "Изисква се одобрение на устройството. Изберете начин за одобрение по-долу:" }, + "deviceApprovalRequiredV2": { + "message": "Необходимо е одобрение на устройството" + }, + "selectAnApprovalOptionBelow": { + "message": "Изберете начин за одобряване по-долу" + }, "rememberThisDevice": { "message": "Запомняне на това устройство" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Липсва е-поща на потребителя" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Не е намерена е-поща на активен потребител. Ще бъдете отписан(а)." + }, "deviceTrusted": { "message": "Устройството е доверено" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Не могат да бъдат открити свободни портове за еднократната идентификация." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, + "authorize": { + "message": "Упълномощаване" + }, + "deny": { + "message": "Отказване" + }, + "sshkeyApprovalTitle": { + "message": "Потвърждаване на употребата на SSH ключа" + }, + "sshkeyApprovalMessageInfix": { + "message": "иска достъп до" + }, + "unknownApplication": { + "message": "Приложение" + }, + "sshKeyPasswordUnsupported": { + "message": "Внасянето на SSH ключ, които са защитени с парола, все още не се поддържа" + }, + "invalidSshKey": { + "message": "SSH ключът е неправилен" + }, + "sshKeyTypeUnsupported": { + "message": "Типът на SSH ключа не се поддържа" + }, + "importSshKeyFromClipboard": { + "message": "Внасяне на ключ от буфера за обмен" + }, + "sshKeyPasted": { + "message": "SSH ключът е внесен успешно" + }, "fileSavedToDevice": { "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": "Промяна на е-пощата" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index cc824c57f69..069c58d751d 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "সুরক্ষিত নোট" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "ফোল্ডারসমূহ" }, @@ -177,6 +180,63 @@ "address": { "message": "ঠিকানা" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "প্রিমিয়াম আবশ্যক" }, @@ -189,6 +249,20 @@ "error": { "message": "ত্রুটি" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "জানুয়ারী" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "পাসওয়ার্ড অনুলিপিত করুন" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "সার্ভার URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "পাসওয়ার্ড ইতিহাস" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "পরিষ্কার", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "তালিকার জন্য কোনও পাসওয়ার্ড নেই।" }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 9de9617c8b3..f5052559de9 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sigurna bilješka" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folderi" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresa" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Potrebno premium članstvo" }, @@ -189,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopirajte lozinku" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL servera" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Historija uređivanja lozinke" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Obriši", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nema lozinki za prikazati." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Poništi" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Potvrdi za Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Otključaj koristeći Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 39791d09a2b..9d82ad59fad 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Carpetes" }, @@ -177,6 +180,63 @@ "address": { "message": "Adreça" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium requerit" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gener" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copia contrasenya" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL del servidor" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Historial de les contrasenyes" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Esborra", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "No hi ha cap contrasenya a llistar." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Desfés" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verifica per Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Desbloqueja amb Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipus de nom d'usuari" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "S'ha enviat una notificació al vostre dispositiu." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Assegureu-vos que la vostra caixa forta estiga desbloquejada i que la frase d'empremta digital coincidisca amb l'altre dispositiu." }, "fingerprintPhraseHeader": { "message": "Frase d'empremta digital" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "L'inici de sessió amb el dispositiu ha d'estar activat a la configuració de l'aplicació Bitwarden. Necessiteu una altra opció?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Veure totes les opcions d'inici de sessió" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contrasenya feble identificada i trobada en una filtració de dades. Utilitzeu una contrasenya única i segura per protegir el vostre compte. Esteu segur que voleu utilitzar aquesta contrasenya?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comproveu les filtracions de dades conegudes per a aquesta contrasenya" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Actualització de configuració recomanada" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Cal l'aprovació del dispositiu. Seleccioneu una opció d'aprovació a continuació:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Recorda aquest dispositiu" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Falta el correu electrònic de l'usuari" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositiu de confiança" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 555014f09a1..83f2840b724 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Zabezpečená poznámka" }, + "typeSshKey": { + "message": "SSH klíč" + }, "folders": { "message": "Složky" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresa" }, + "sshPrivateKey": { + "message": "Soukromý klíč" + }, + "sshPublicKey": { + "message": "Veřejný klíč" + }, + "sshFingerprint": { + "message": "Otisk prstu" + }, + "sshKeyAlgorithm": { + "message": "Typ klíče" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 bitový" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 bitový" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 bitový" + }, + "sshKeyGenerated": { + "message": "Byl vygenerován nový SSH klíč" + }, + "sshKeyWrongPassword": { + "message": "Zadané heslo není správné." + }, + "importSshKey": { + "message": "Importovat" + }, + "confirmSshKeyPassword": { + "message": "Potvrdit heslo" + }, + "enterSshKeyPasswordDesc": { + "message": "Zadejte heslo pro SSH klíč." + }, + "enterSshKeyPassword": { + "message": "Zadejte heslo" + }, + "sshAgentUnlockRequired": { + "message": "Pro schválení požadavku na SSH klíč odemkněte Váš trezor." + }, + "sshAgentUnlockTimeout": { + "message": "Vypršel časový limit požadavku SSH klíče." + }, + "enableSshAgent": { + "message": "Povolit SSH agenta" + }, + "enableSshAgentDesc": { + "message": "Povolí SSH agentovi podepisovat SSH požadavky přímo z Vašeho trezoru." + }, + "enableSshAgentHelp": { + "message": "SSH agent je služba určená vývojářům; umožňuje Vám podepisovat požadavky SSH přímo z Vašeho trezoru." + }, "premiumRequired": { "message": "Je vyžadováno členství Premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Leden" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopírovat heslo" }, + "regenerateSshKey": { + "message": "Znovu vygenerovat SSH klíč" + }, + "copySshPrivateKey": { + "message": "Kopírovat soukromý SSH klíč" + }, "copyPassphrase": { "message": "Kopírovat heslovou frázi", "description": "Copy passphrase to clipboard" @@ -827,7 +907,7 @@ "message": "Přidejte další poskytovatele, kteří jsou lépe podporováni v různých zařízeních (například ověřovací aplikace)." }, "twoStepOptions": { - "message": "Možnosti dvoufázového přihlášení" + "message": "Volby dvoufázového přihlášení" }, "selfHostedEnvironment": { "message": "Vlastní hostované prostředí" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL serveru" }, + "authenticationTimeout": { + "message": "Časový limit ověření" + }, + "authenticationSessionTimedOut": { + "message": "Vypršel časový limit relace ověřování. Restartujte proces přihlášení." + }, "selfHostBaseUrl": { "message": "Adresa URL serveru vlastního hostování", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Historie hesel" }, + "generatorHistory": { + "message": "Historie generátoru" + }, + "clearGeneratorHistoryTitle": { + "message": "Vymazat historii generátoru" + }, + "cleargGeneratorHistoryDescription": { + "message": "Pokud budete pokračovat, všechny položky budou trvale smazány z historie generátoru. Jste si jisti, že chcete pokračovat?" + }, "clear": { "message": "Vymazat", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nejsou k dispozici žádná hesla." }, + "clearHistory": { + "message": "Vymazat historii" + }, + "nothingToShow": { + "message": "Nic k zobrazení" + }, + "nothingGeneratedRecently": { + "message": "Nedávno jste nic nevygenerovali" + }, "undo": { "message": "Zpět" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Ověřte se pro Bitwarden." }, - "polkitConsentMessage": { - "message": "Ověřte se pro odemknutí Bitwardenu." - }, "unlockWithTouchId": { "message": "Odemknout pomocí Touch ID" }, @@ -1969,7 +2070,7 @@ "message": "Nápověda k Vašemu heslu nemůže být stejná jako Vaše heslo." }, "personalOwnershipPolicyInEffect": { - "message": "Zásady organizace ovlivňují možnosti vlastnictví." + "message": "Zásady organizace ovlivňují volby vlastnictví." }, "personalOwnershipPolicyInEffectImports": { "message": "Zásady organizace zablokovaly importování položek do Vašeho osobního trezoru." @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Vygenerovat e-mail" }, - "generatorBoundariesHint": { - "message": "Hodnota musí být mezi $MIN$ a $MAX$", + "spinboxBoundariesHint": { + "message": "Hodnota musí být mezi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Použijte $RECOMMENDED$ nebo více znaků k vytvoření silného hesla.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Použijte $RECOMMENDED$ slov nebo více slov k vytvoření silné heslové fráze.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Typ uživatelského jména" }, @@ -2580,7 +2701,7 @@ "message": "K položkám v deaktivované organizaci nemáte přístup. Požádejte o pomoc vlastníka organizace." }, "neverLockWarning": { - "message": "Opravdu chcete použít volbu \"Nikdy\"? Nastavením možností uzamčení na \"Nikdy\" bude šifrovací klíč k trezoru uložen přímo ve Vašem zařízení. Pokud tuto možnost použijete, měli byste Vaše zařízení řádně zabezpečit a chránit." + "message": "Opravdu chcete použít volbu \"Nikdy\"? Nastavením volby uzamčení na \"Nikdy\" bude šifrovací klíč k trezoru uložen přímo ve Vašem zařízení. Pokud tuto možnost použijete, měli byste Vaše zařízení řádně zabezpečit a chránit." }, "vault": { "message": "Trezor" @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení." }, + "aNotificationWasSentToYourDevice": { + "message": "Na Vaše zařízení bylo odesláno oznámení" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Ujistěte se, že je Váš účet odemčen a fráze otisku prstu se shoduje s tou na druhém zařízení" + }, + "needAnotherOptionV1": { + "message": "Potřebujete další volby?" + }, "fingerprintMatchInfo": { "message": "Ujistěte se, že je Váš trezor odemčen a fráze otisku prstu se shodují s druhým zařízením." }, "fingerprintPhraseHeader": { "message": "Fráze otisku prstu" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Budete upozorněni, jakmile bude žádost schválena" + }, "needAnotherOption": { "message": "Přihlášení zařízením musí být nastaveno v aplikaci Bitwarden pro počítač. Potřebujete další volby?" }, + "viewAllLogInOptions": { + "message": "Zobrazit všechny volby přihlášení" + }, "viewAllLoginOptions": { "message": "Zobrazit všechny volby přihlášení" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slabé heslo bylo nalezeno mezi odhalenými hesly. K zabezpečení Vašeho účtu používejte silné a jedinečné heslo. Opravdu chcete používat toto heslo?" }, + "useThisPassword": { + "message": "Použít toto heslo" + }, + "useThisUsername": { + "message": "Použít toto uživatelské jméno" + }, "checkForBreaches": { "message": "Zkontrolovat heslo, zda nebylo odhaleno" }, + "loggedInExclamation": { + "message": "Přihlášeno!" + }, "important": { "message": "Důležité:" }, @@ -2776,8 +2921,17 @@ "windowsBiometricUpdateWarningTitle": { "message": "Aktualizace doporučených nastavení" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamatovat si toto zařízení pro bezproblémové budoucí přihlášení" + }, "deviceApprovalRequired": { - "message": "Vyžaduje se schválení zařízení. Vyberte možnost schválení níže:" + "message": "Vyžaduje se schválení zařízení. Vyberte volbu schválení níže:" + }, + "deviceApprovalRequiredV2": { + "message": "Vyžaduje se schválení zařízení" + }, + "selectAnApprovalOptionBelow": { + "message": "Vyberte volbu schválení níže" }, "rememberThisDevice": { "message": "Zapamatovat toto zařízení" @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Chybí e-mail uživatele" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktivní uživatelský e-mail nebyl nalezen. Budete odhlášeni." + }, "deviceTrusted": { "message": "Zařízení zařazeno mezi důvěryhodné" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Pro přihlášení SSO nebyly nalezeny žádné volné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrické odemknutí není k dispozici, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, + "authorize": { + "message": "Autorizovat" + }, + "deny": { + "message": "Zamítnout" + }, + "sshkeyApprovalTitle": { + "message": "Potvrdit použití SSH klíče" + }, + "sshkeyApprovalMessageInfix": { + "message": "žádá o přístup k" + }, + "unknownApplication": { + "message": "Aplikace" + }, + "sshKeyPasswordUnsupported": { + "message": "Import šifrovaných SSH klíčů není zatím podporován" + }, + "invalidSshKey": { + "message": "SSH klíč je neplatný" + }, + "sshKeyTypeUnsupported": { + "message": "Typ SSH klíče není podporován" + }, + "importSshKeyFromClipboard": { + "message": "Importovat klíč ze schránky" + }, + "sshKeyPasted": { + "message": "SSH klíč byl úspěšně importován" + }, "fileSavedToDevice": { "message": "Soubor byl uložen. Můžete jej nalézt ve stažené složce v zařízení." + }, + "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" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 8d8e807026d..f4e0853a933 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index a4bb7ce4e4a..ebe74818f47 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sikret notat" }, + "typeSshKey": { + "message": "SSH-nøgle" + }, "folders": { "message": "Mapper" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresse" }, + "sshPrivateKey": { + "message": "Privat nøgle" + }, + "sshPublicKey": { + "message": "Offentlig nøgle" + }, + "sshFingerprint": { + "message": "Fingeraftryk" + }, + "sshKeyAlgorithm": { + "message": "Nøgletype" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "En ny SSH-nøgle er genereret" + }, + "sshKeyWrongPassword": { + "message": "Den angivne adgangskode er forkert." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Bekræft adgangskode" + }, + "enterSshKeyPasswordDesc": { + "message": "Angiv adgangskoden til SSH-nøglen." + }, + "enterSshKeyPassword": { + "message": "Angiv adgangskode" + }, + "sshAgentUnlockRequired": { + "message": "Oplås boksen op for at godkende SSH-nøgleanmodningen." + }, + "sshAgentUnlockTimeout": { + "message": "SSH-nøgleanmodning fik timeout." + }, + "enableSshAgent": { + "message": "Aktivér SSH-agent" + }, + "enableSshAgentDesc": { + "message": "Aktivér SSH-agenten for at signere SSH-forespørgsler direkte fra Bitwarden-boksen." + }, + "enableSshAgentHelp": { + "message": "SSH-agent er en tjeneste rettet mod udviklere, der muliggør at signere SSH-forespørgsler direkte fra Bitwarden-boksen." + }, "premiumRequired": { "message": "Premium kræves" }, @@ -189,6 +249,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopiér adgangskode" }, + "regenerateSshKey": { + "message": "Regenerér SSH-nøgle" + }, + "copySshPrivateKey": { + "message": "Kopiér SSH privat nøgle" + }, "copyPassphrase": { "message": "Kopiér adgangssætning", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server-URL" }, + "authenticationTimeout": { + "message": "Godkendelsestimeout" + }, + "authenticationSessionTimedOut": { + "message": "Godkendelsessessionen fik timeout. Genstart loginprocessen." + }, "selfHostBaseUrl": { "message": "URL til selv-hostet server", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Adgangskodehistorik" }, + "generatorHistory": { + "message": "Generatorhistorik" + }, + "clearGeneratorHistoryTitle": { + "message": "Ryd generatorhistorik" + }, + "cleargGeneratorHistoryDescription": { + "message": "Fortsætter man, slettes alle poster permanent fra generatorhistorikken. Sikker på, at handlingen skal udføres?" + }, "clear": { "message": "Ryd", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Der er ingen kodeord at vise." }, + "clearHistory": { + "message": "Ryd historik" + }, + "nothingToShow": { + "message": "Intet at vise" + }, + "nothingGeneratedRecently": { + "message": "Intet genereret for nylig" + }, "undo": { "message": "Fortryd" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bekræft for Bitwarden." }, - "polkitConsentMessage": { - "message": "Godkend for at oplåse Bitwarden." - }, "unlockWithTouchId": { "message": "Oplås med Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generér e-mail" }, - "generatorBoundariesHint": { - "message": "Værdi skal være mellem $MIN$ og $MAX$", + "spinboxBoundariesHint": { + "message": "Værdi skal være mellem $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Brug minimum $RECOMMENDED$ tegn for at generere en stærk adgangskode.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Brug minimum $RECOMMENDED$ ord for at generere en stærk adgangssætning.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Brugernavnstype" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." }, + "aNotificationWasSentToYourDevice": { + "message": "En notifikation er sendt til enheden" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Sørg for, at boksen er oplåst, samt at fingeraftrykssætningen matcher på den anden enhed" + }, + "needAnotherOptionV1": { + "message": "Behov for en anden mulighed?" + }, "fingerprintMatchInfo": { "message": "Sørg for, at din boks er oplåst, og at fingeraftrykssætningen matcher den anden enheds." }, "fingerprintPhraseHeader": { "message": "Fingeraftrykssætning" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Man vil få besked, når anmodningen er godkendt" + }, "needAnotherOption": { "message": "Log ind med enhed skal være opsat i indstillingerne i Bitwarden mobil-appen. Behov for en anden mulighed?" }, + "viewAllLogInOptions": { + "message": "Vis alle indlogningsmuligheder" + }, "viewAllLoginOptions": { "message": "Vis alle indlogningsmuligheder" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Svag adgangskode identificeret og fundet i datalæk. Brug en unik adgangskode til at beskytte din konto. Sikker på, at at denne adgangskode skal bruges?" }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, "checkForBreaches": { "message": "Tjek kendte datalæk for denne adgangskode" }, + "loggedInExclamation": { + "message": "Logget ind!" + }, "important": { "message": "Vigtigt:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Anbefalet indstillingsopdatering" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Husk denne enhed for at gøre fremtidige indlogninger gnidningsløse" + }, "deviceApprovalRequired": { "message": "Enhedsgodkendelse kræves. Vælg en godkendelsesmulighed nedenfor:" }, + "deviceApprovalRequiredV2": { + "message": "Enhedsgodkendelse kræves" + }, + "selectAnApprovalOptionBelow": { + "message": "Vælg en godkendelsesmulighed nedenfor" + }, "rememberThisDevice": { "message": "Husk denne enhed" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Brugers e-mail mangler" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiv bruger e-mail ikke fundet. Man logges ud." + }, "deviceTrusted": { "message": "Enhed betroet" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Ingen ledige porte fundet til SSO-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, + "authorize": { + "message": "Godkend" + }, + "deny": { + "message": "Afvis" + }, + "sshkeyApprovalTitle": { + "message": "Bekræft brug af SSH-nøgle" + }, + "sshkeyApprovalMessageInfix": { + "message": "anmoder om adgang til" + }, + "unknownApplication": { + "message": "En applikation" + }, + "sshKeyPasswordUnsupported": { + "message": "Import af adgangskodebeskyttede SSH-nøgler understøttes endnu ikke" + }, + "invalidSshKey": { + "message": "SSH-nøglen er ugyldig" + }, + "sshKeyTypeUnsupported": { + "message": "SSH-nøgletypen understøttes ikke" + }, + "importSshKeyFromClipboard": { + "message": "Importér nøgle fra udklipsholder" + }, + "sshKeyPasted": { + "message": "SSH-nøgle er importeret" + }, "fileSavedToDevice": { "message": "Fil gemt på enheden. Håndtér fra enhedens downloads." + }, + "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, muligvis ikke" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Ja, e-mailadressen kan pålideligt tilgås" + }, + "turnOnTwoStepLogin": { + "message": "Slå totrins-login til" + }, + "changeAcctEmail": { + "message": "Skift kontoe-mailadresse" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 070da06de04..133d98c3faa 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sichere Notiz" }, + "typeSshKey": { + "message": "SSH-Schlüssel" + }, "folders": { "message": "Ordner" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresse" }, + "sshPrivateKey": { + "message": "Privater Schlüssel" + }, + "sshPublicKey": { + "message": "Öffentlicher Schlüssel" + }, + "sshFingerprint": { + "message": "Fingerabdruck" + }, + "sshKeyAlgorithm": { + "message": "Schlüsseltyp" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Ein neuer SSH-Schlüssel wurde generiert" + }, + "sshKeyWrongPassword": { + "message": "Dein eingegebenes Passwort ist falsch." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Passwort bestätigen" + }, + "enterSshKeyPasswordDesc": { + "message": "Gib das Passwort für den SSH-Schlüssel ein." + }, + "enterSshKeyPassword": { + "message": "Passwort eingeben" + }, + "sshAgentUnlockRequired": { + "message": "Bitte entsperre deinen Tresor, um die SSH-Schlüsselanfrage zu genehmigen." + }, + "sshAgentUnlockTimeout": { + "message": "Zeitüberschreitung bei der SSH-Schlüsselanfrage." + }, + "enableSshAgent": { + "message": "SSH-Agent aktivieren" + }, + "enableSshAgentDesc": { + "message": "Aktiviere den SSH-Agenten, um SSH-Anfragen direkt aus deinem Bitwarden-Tresor aus zu signieren." + }, + "enableSshAgentHelp": { + "message": "Der SSH-Agent ist ein Dienst, der sich an Entwickler richtet, mit dem du SSH-Anfragen direkt aus deinem Bitwarden-Tresor aus signieren kannst." + }, "premiumRequired": { "message": "Premium notwendig" }, @@ -189,6 +249,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kundensupport kontaktieren", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Passwort kopieren" }, + "regenerateSshKey": { + "message": "SSH-Schlüssel neu generieren" + }, + "copySshPrivateKey": { + "message": "Privaten SSH-Schlüssel kopieren" + }, "copyPassphrase": { "message": "Passphrase kopieren", "description": "Copy passphrase to clipboard" @@ -576,7 +656,7 @@ "message": "Mit Passkey anmelden" }, "loginWithDevice": { - "message": "Mit Gerät anmelden" + "message": "Anmelden mit einem anderen Gerät" }, "useSingleSignOn": { "message": "Single Sign-on verwenden" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server-URL" }, + "authenticationTimeout": { + "message": "Authentifizierungs-Timeout" + }, + "authenticationSessionTimedOut": { + "message": "Die Authentifizierungssitzung ist abgelaufen. Bitte starte den Anmeldeprozess neu." + }, "selfHostBaseUrl": { "message": "Selbst gehostete Server-URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Passwortverlauf" }, + "generatorHistory": { + "message": "Generator-Verlauf" + }, + "clearGeneratorHistoryTitle": { + "message": "Generator-Verlauf löschen" + }, + "cleargGeneratorHistoryDescription": { + "message": "Wenn du fortfährst, werden alle Einträge dauerhaft aus dem Generator-Verlauf gelöscht. Bist du sicher, dass du fortfahren möchtest?" + }, "clear": { "message": "Leeren", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Keine Einträge zum Anzeigen vorhanden." }, + "clearHistory": { + "message": "Verlauf löschen" + }, + "nothingToShow": { + "message": "Nichts anzuzeigen" + }, + "nothingGeneratedRecently": { + "message": "Du hast in letzter Zeit nichts generiert" + }, "undo": { "message": "Rückgängig" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Für Bitwarden verifizieren." }, - "polkitConsentMessage": { - "message": "Authentifizieren, um Bitwarden zu entsperren." - }, "unlockWithTouchId": { "message": "Mit Touch ID entsperren" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "E-Mail generieren" }, - "generatorBoundariesHint": { - "message": "Wert muss zwischen $MIN$ und $MAX$ liegen", + "spinboxBoundariesHint": { + "message": "Wert muss zwischen $MIN$ und $MAX$ liegen.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Verwende $RECOMMENDED$ oder mehr Zeichen, um ein starkes Passwort zu generieren.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Verwende $RECOMMENDED$ oder mehr Wörter, um eine starke Passphrase zu generieren.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Benutzernamentyp" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet." }, + "aNotificationWasSentToYourDevice": { + "message": "Eine Benachrichtigung wurde an dein Gerät gesendet" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Stelle sicher, dass dein Konto entsperrt ist und die Fingerabdruck-Phrase mit der vom anderen Gerät übereinstimmt" + }, + "needAnotherOptionV1": { + "message": "Brauchst du eine andere Option?" + }, "fingerprintMatchInfo": { "message": "Bitte stelle sicher, dass dein Tresor entsperrt ist und die Fingerabdruck-Phrase mit dem anderen Gerät übereinstimmt." }, "fingerprintPhraseHeader": { "message": "Fingerabdruck-Phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Du wirst benachrichtigt, sobald die Anfrage genehmigt wurde" + }, "needAnotherOption": { "message": "Die Anmeldung über ein Gerät muss in den Einstellungen der Bitwarden App eingerichtet werden. Benötigst du eine andere Option?" }, + "viewAllLogInOptions": { + "message": "Alle Anmeldeoptionen anzeigen" + }, "viewAllLoginOptions": { "message": "Alle Anmeldeoptionen anzeigen" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Schwaches Passwort erkannt und in einem Datendiebstahl gefunden. Verwende ein starkes und einzigartiges Passwort, um dein Konto zu schützen. Bist du sicher, dass du dieses Passwort verwenden möchtest?" }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, "checkForBreaches": { "message": "Bekannte Datendiebstähle auf dieses Passwort überprüfen" }, + "loggedInExclamation": { + "message": "Angemeldet!" + }, "important": { "message": "Wichtig:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Empfohlene Aktualisierung der Einstellungen" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Dieses Gerät merken, um zukünftige Anmeldungen reibungslos zu gestalten" + }, "deviceApprovalRequired": { "message": "Geräte-Genehmigung erforderlich. Wähle unten eine Genehmigungsoption aus:" }, + "deviceApprovalRequiredV2": { + "message": "Geräte-Genehmigung erforderlich" + }, + "selectAnApprovalOptionBelow": { + "message": "Wähle unten eine Genehmigungsoption aus" + }, "rememberThisDevice": { "message": "Dieses Gerät merken" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "E-Mail-Adresse des Benutzers fehlt" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktive Benutzer-E-Mail-Adresse nicht gefunden. Du wirst abgemeldet." + }, "deviceTrusted": { "message": "Gerät wird vertraut" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Es konnten keine freien Ports für die SSO-Anmeldung gefunden werden." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." + }, + "authorize": { + "message": "Autorisieren" + }, + "deny": { + "message": "Ablehnen" + }, + "sshkeyApprovalTitle": { + "message": "Verwendung des SSH-Schlüssels bestätigen" + }, + "sshkeyApprovalMessageInfix": { + "message": "fragt den Zugriff an für" + }, + "unknownApplication": { + "message": "Eine Anwendung" + }, + "sshKeyPasswordUnsupported": { + "message": "Das Importieren passwortgeschützter SSH-Schlüssel wird noch nicht unterstützt" + }, + "invalidSshKey": { + "message": "Der SSH-Schlüssel ist ungültig" + }, + "sshKeyTypeUnsupported": { + "message": "Der SSH-Schlüsseltyp wird nicht unterstützt" + }, + "importSshKeyFromClipboard": { + "message": "Schlüssel aus Zwischenablage importieren" + }, + "sshKeyPasted": { + "message": "SSH-Schlüssel erfolgreich importiert" + }, "fileSavedToDevice": { "message": "Datei auf Gerät gespeichert. Greife darauf über die Downloads deines Geräts zu." + }, + "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" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index ed067effeb2..145f386c6b9 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Ασφαλής σημείωση" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Φάκελοι" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Καλωσορίσατε και πάλι" + "message": "Welcome back" }, "moveToOrgDesc": { "message": "Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτό το στοιχείο. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε." @@ -177,6 +180,63 @@ "address": { "message": "Διεύθυνση" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Απαιτείται Premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ιανουάριος" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Αντιγραφή κωδικού πρόσβασης" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Αντιγραφή φράσης πρόσβασης", "description": "Copy passphrase to clipboard" @@ -558,7 +638,7 @@ "message": "Δημιουργία λογαριασμού" }, "newToBitwarden": { - "message": "Νέος/α στο Bitwarden;" + "message": "New to Bitwarden?" }, "setAStrongPassword": { "message": "Ορίστε έναν ισχυρό κωδικό πρόσβασης" @@ -570,16 +650,16 @@ "message": "Είσοδος" }, "logInToBitwarden": { - "message": "Σύνδεση στο Bitwarden" + "message": "Log in to Bitwarden" }, "logInWithPasskey": { - "message": "Σύνδεση με κλειδί πρόσβασης" + "message": "Log in with passkey" }, "loginWithDevice": { - "message": "Σύνδεση με χρήση συσκευής" + "message": "Log in with device" }, "useSingleSignOn": { - "message": "Χρήση ενιαίας σύνδεσης" + "message": "Use single sign-on" }, "submit": { "message": "Υποβολή" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL Διακομιστή" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "URL διακομιστή αυτο-φιλοξενίας", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Ιστορικό κωδικού πρόσβασης" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Εκκαθάριση", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Δεν υπάρχουν κωδικοί στη λίστα." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Αναίρεση" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Επαληθεύστε για το Bitwarden." }, - "polkitConsentMessage": { - "message": "Αυθεντικοποίηση για ξεκλείδωμα του Bitwarden." - }, "unlockWithTouchId": { "message": "Ξεκλείδωμα με Touch ID" }, @@ -1684,10 +1785,10 @@ "message": "Η διαγραφή του λογαριασμού σας είναι μόνιμη. Δεν μπορεί να αναιρεθεί." }, "cannotDeleteAccount": { - "message": "Αδυναμία διαγραφής λογαριασμού" + "message": "Cannot delete account" }, "cannotDeleteAccountDesc": { - "message": "Αυτή η ενέργεια δεν μπορεί να ολοκληρωθεί επειδή ο λογαριασμός σας ανήκει σε έναν οργανισμό. Επικοινωνήστε με το διαχειριστή του οργανισμού σας για πρόσθετες λεπτομέρειες." + "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." }, "accountDeleted": { "message": "Ο λογαριασμός διαγράφηκε" @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Δημιουργία διεύθυνσης ηλ. ταχυδρομείου" }, - "generatorBoundariesHint": { - "message": "Η τιμή πρέπει να είναι μεταξύ $MIN$ και $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Τύπος ονόματος χρήστη" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Παρακαλώ βεβαιωθείτε ότι το θησαυ/κιό σας είναι ξεκλείδωτο και η φράση δακτυλικών αποτυπωμάτων ταιριάζει με την άλλη συσκευή." }, "fingerprintPhraseHeader": { "message": "Φράση δακτυλικών αποτυπωμάτων" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Η σύνδεση με τη χρήση συσκευής πρέπει να οριστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε κάποια άλλη επιλογή;" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Δείτε όλες τις επιλογές σύνδεσης" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Βρέθηκε και ταυτοποιήθηκε αδύναμος κωδικός σε μια διαρροή δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Σημαντικό:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Ενημέρωση Προτεινόμενων Ρυθμίσεων" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Απαιτείται έγκριση συσκευής. Επιλέξτε μια επιλογή έγκρισης παρακάτω:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Απομνημόνευση αυτής της συσκευής" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Η διεύθυνση ηλ. ταχυδρομείου του χρήστη λείπει" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Αξιόπιστη συσκευή" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Δεν βρέθηκαν ελεύθερες θύρες για τη σύνδεση sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "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" } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 83fa064a35e..bca12f16a7d 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -207,6 +207,21 @@ "sshKeyGenerated": { "message": "A new SSH key was generated" }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, "sshAgentUnlockRequired": { "message": "Please unlock your vault to approve the SSH key request." }, @@ -234,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -904,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1371,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1378,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1695,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1734,10 +1784,10 @@ "deleteAccountWarning": { "message": "Deleting your account is permanent. It cannot be undone." }, - "cannotDeleteAccount":{ + "cannotDeleteAccount": { "message": "Cannot delete account" }, - "cannotDeleteAccountDesc":{ + "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." }, "accountDeleted": { @@ -2444,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2458,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2669,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2794,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2827,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2882,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3276,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3308,5 +3438,50 @@ }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 5c994e8ba56..acca06b8b4f 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorise" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 34e269d5d70..122217dae9d 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorise" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index b6500674e44..a4621439f50 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sekura noto" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Dosierujoj" }, @@ -177,6 +180,63 @@ "address": { "message": "Adreso" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januaro" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Pasvorta historio" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Malfari" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Malŝlosi per Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 1bdc8ab8340..1915002b6bd 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Carpetas" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Bienvenido de nuevo" }, "moveToOrgDesc": { "message": "Elige una organización a la que deseas mover este objeto. Moviendo a una organización transfiere la propiedad del objeto a esa organización. Ya no serás el dueño directo de este objeto una vez que haya sido movido." @@ -177,6 +180,63 @@ "address": { "message": "Dirección" }, + "sshPrivateKey": { + "message": "Clave privada" + }, + "sshPublicKey": { + "message": "Clave pública" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Tipo de clave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "La contraseña introducida es incorrecta." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirmar contraseña" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Introducir la contraseña" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Habilitar agente SSH" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium requerido" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copiar contraseña" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -432,7 +512,7 @@ "message": "Carácteres especiales (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Incluir", "description": "Card header for password generator include block" }, "uppercaseDescription": { @@ -452,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Incluir números", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -558,7 +638,7 @@ "message": "Crear cuenta" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "¿Nuevo en Bitwarden?" }, "setAStrongPassword": { "message": "Establece una contraseña fuerte" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL del servidor" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1004,13 +1090,13 @@ "message": "Your account is locked" }, "or": { - "message": "or" + "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloquear con biométricos" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloquear con contraseña maestra" }, "unlock": { "message": "Desbloquear" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Historial de contraseñas" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Limpiar", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "No hay contraseñas que listar." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Deshacer" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verificar para Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Desbloquear con Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo de nombre de usuario" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Por favor, asegúrese de que su caja fuerte está desbloqueada y la frase de huella dactilar coincide con el otro dispositivo." }, "fingerprintPhraseHeader": { "message": "Frase de huella dactilar" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Iniciar sesión con el dispositivo debe estar habilitado en los ajustes de la aplicación móvil Bitwarden. ¿Necesitas otra opción?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Ver todas las opciones de inicio de sesión" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contraseña débil encontrada en una filtración de datos. Utilice una contraseña única para proteger su cuenta. ¿Está seguro de que desea utilizar una contraseña comprometida?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comprobar filtración de datos conocidos para esta contraseña" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Importante:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Actualización de ajustes recomendados" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Se requiere aprobación del dispositivo. Selecciona una opción de aprobación a continuación:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Recordar este dispositivo" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Falta el correo electrónico del usuario" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositivo de confianza" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index c129a6c4bb0..8396316416b 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Turvaline märkus" }, + "typeSshKey": { + "message": "SSH võti" + }, "folders": { "message": "Kaustad" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Tere tulemast tagasi" }, "moveToOrgDesc": { "message": "Vali organisatsioon, kuhu soovid seda kirjet teisaldada. Teisaldamisega saab kirje omanikuks organisatsioon. Pärast kirje teisaldamist ei ole sa enam selle otsene omanik." @@ -177,6 +180,63 @@ "address": { "message": "Aadress" }, + "sshPrivateKey": { + "message": "Privaatvõti" + }, + "sshPublicKey": { + "message": "Avalik võti" + }, + "sshFingerprint": { + "message": "Sõrmejälg" + }, + "sshKeyAlgorithm": { + "message": "Võtme tüüp" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bitine" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bitine" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bitine" + }, + "sshKeyGenerated": { + "message": "Uus SSh võti on loodud" + }, + "sshKeyWrongPassword": { + "message": "Sinu sisestatud salasõna pole õige." + }, + "importSshKey": { + "message": "Impordi" + }, + "confirmSshKeyPassword": { + "message": "Kinnita salasõna" + }, + "enterSshKeyPasswordDesc": { + "message": "Sisesta selle SSH võtme salasõna." + }, + "enterSshKeyPassword": { + "message": "Sisesta salasõna" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Vajalik on Preemium versioon" }, @@ -189,6 +249,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Jaanuar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopeeri parool" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Serveri URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1041,7 +1127,7 @@ "message": "Hoidla ajalõpp" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Aegumine" }, "vaultTimeoutDesc": { "message": "Vali millal saabub hoidla ajalõpp ning sooritatakse valitud tegevus." @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Kopeeri e-posti aadress" }, "copySecurityCode": { "message": "Kopeeri turvakood", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Paroolide ajalugu" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Tühjenda", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Puuduvad paroolid, mida kuvada." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Võta tagasi" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Kinnita Bitwardenisse sisselogimine." }, - "polkitConsentMessage": { - "message": "Autentiteeri ennast Bitwardeni avamiseks." - }, "unlockWithTouchId": { "message": "Lukusta lahti Touch ID-ga" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Kasutajanime tüüp" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Veendu, et sinu hoidla on avatud ja unikaalne sõnajada ühtib teise seadmega." }, "fingerprintPhraseHeader": { "message": "Unikaalne sõnajada" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Bitwardeni rakenduse seadistuses peab olema konfigureeritud sisselogimine läbi seadme. Vajad teist valikut?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Vaata kõiki valikuid" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Tuvastati nõrk ning andmelekkes lekkinud ülemparool. Kasuta konto paremaks turvamiseks tugevamat parooli. Oled kindel, et soovid nõrga parooliga jätkata?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Otsi seda parooli teadaolevatest andmeleketest" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Tähtis:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Soovitatud Muudatus Seadetes" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Seadme kinnitamine on nõutud. Palun vali kuidas soovid seda teha:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Hoia see seade meeles" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Kasutaja email puudub" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Usaldusväärne seade" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "SSO-ga sisselogimiseks ei leitud ühtegi vaba porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "Fail salvestatud. Halda oma seadmesse allalaaditud faile." + }, + "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" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 46d29321515..2daed855e52 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Ohar segurua" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Karpetak" }, @@ -177,6 +180,63 @@ "address": { "message": "Helbidea" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium izatea beharrezkoa da" }, @@ -189,6 +249,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Urtarrila" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopiatu pasahitza" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Zerbitzariaren URL-a" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Pasahitz historia" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Ezabatu", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Ez dago erakusteko pasahitzik." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Desegin" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Egiaztatu Bitwarden-entzako." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Desblokeatu Touch ID-arekin" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Erabiltzaile izen mota" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 65ad1be1cbb..b79dc2d90ee 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "یادداشت امن" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "پوشه‌ها" }, @@ -177,6 +180,63 @@ "address": { "message": "نشانی" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "در نسخه پرمیوم کار می‌کند" }, @@ -189,6 +249,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ژانویه" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "کپی کلمه عبور" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "نشانی اینترنتی سرور" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "تاریخچه کلمه عبور" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "پاک کردن", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "بازگرداندن" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "تأیید برای Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "باز کردن با Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "نوع نام کاربری" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "لطفاً مطمئن شوید که قفل گاوصندوق شما باز است و عبارت اثر انگشت در دستگاه دیگر مطابقت دارد." }, "fingerprintPhraseHeader": { "message": "عبارت اثر انگشت" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "مشاهده همه گزینه‌های ورود به سیستم" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "مهم:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "به‌روز رسانی تنظیمات توصیه شده" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "ایمیل کاربر وجود ندارد" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index cf6025e0423..2d0a8cae996 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Salattu muistio" }, + "typeSshKey": { + "message": "SSH-avain" + }, "folders": { "message": "Kansiot" }, @@ -177,6 +180,63 @@ "address": { "message": "Osoite" }, + "sshPrivateKey": { + "message": "Yksityinen avain" + }, + "sshPublicKey": { + "message": "Julkinen avain" + }, + "sshFingerprint": { + "message": "Sormenjälki" + }, + "sshKeyAlgorithm": { + "message": "Avaintyyppi" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Uusi SSH-avain luotiin" + }, + "sshKeyWrongPassword": { + "message": "Syöttämäsi salasana on virheellinen." + }, + "importSshKey": { + "message": "Tuo" + }, + "confirmSshKeyPassword": { + "message": "Vahvista salasana" + }, + "enterSshKeyPasswordDesc": { + "message": "Syötä SSH-avaimen salasana." + }, + "enterSshKeyPassword": { + "message": "Syötä salasana" + }, + "sshAgentUnlockRequired": { + "message": "Ole hyvä ja avaa holvisi hyväksyäksesi SSH-avainpyynnön." + }, + "sshAgentUnlockTimeout": { + "message": "SSH-avainpyyntö aikakatkaistiin." + }, + "enableSshAgent": { + "message": "Ota SSH-agentti käyttöön" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium vaaditaan" }, @@ -189,6 +249,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tammikuu" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopioi salasana" }, + "regenerateSshKey": { + "message": "Luo SSH-avain uudelleen" + }, + "copySshPrivateKey": { + "message": "Kopioi yksityinen SSH-avain" + }, "copyPassphrase": { "message": "Kopioi salalause", "description": "Copy passphrase to clipboard" @@ -558,7 +638,7 @@ "message": "Luo tili" }, "newToBitwarden": { - "message": "Oletko uusi Bitwardenissa?" + "message": "Oletko uusi Bitwarden-käyttäjä?" }, "setAStrongPassword": { "message": "Aseta vahva salasana" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Palvelimen URL" }, + "authenticationTimeout": { + "message": "Todennuksen aikakatkaisu" + }, + "authenticationSessionTimedOut": { + "message": "Todennusistunto aikakatkaistiin. Ole hyvä ja aloita kirjautumisprosessi uudelleen." + }, "selfHostBaseUrl": { "message": "Itse ylläpidetyn palvelimen URL-osoite", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Salasanahistoria" }, + "generatorHistory": { + "message": "Generaattorihistoria" + }, + "clearGeneratorHistoryTitle": { + "message": "Tyhjennä generaattorihistoria" + }, + "cleargGeneratorHistoryDescription": { + "message": "Jos jatkat, kaikki generaattorihistorian kohteet poistetaan. Haluatko varmasti jatkaa?" + }, "clear": { "message": "Tyhjennä", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Ei näytettäviä salasanoja." }, + "clearHistory": { + "message": "Tyhjennä historia" + }, + "nothingToShow": { + "message": "Ei näytettävää" + }, + "nothingGeneratedRecently": { + "message": "Et ole luonut mitään hiljattain" + }, "undo": { "message": "Kumoa" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Vahvista Bitwarden." }, - "polkitConsentMessage": { - "message": "Avaa Bitwardenin lukitus tunnistautumalla." - }, "unlockWithTouchId": { "message": "Avaa Touch ID:llä" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Luo sähköpostiosoite" }, - "generatorBoundariesHint": { - "message": "Arvon tulee olla väliltä $MIN$ - $MAX$", + "spinboxBoundariesHint": { + "message": "Arvon tulee olla väliltä $MIN$—$MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Käytä $RECOMMENDED$ tai useampaa merkkiä vahvan salasanan luomiseksi.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Käytä $RECOMMENDED$ tai useampaa sanaa vahvan salalauseen luomiseksi.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Käyttäjätunnuksen tyyppi" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Laitteellesi on lähetetty ilmoitus." }, + "aNotificationWasSentToYourDevice": { + "message": "Laitteeseesi lähetettiin ilmoitus" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen" + }, + "needAnotherOptionV1": { + "message": "Tarvitsetko toisen vaihtoehdon?" + }, "fingerprintMatchInfo": { "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen." }, "fingerprintPhraseHeader": { "message": "Tunnistelauseke" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Ilmoitamme sinulle, kun pyyntösi on hyväksytty" + }, "needAnotherOption": { "message": "Laitteella kirjautuminen on määritettävä Bitwarden-sovelluksen asetuksista. Tarvitsetko eri vaihtoehdon?" }, + "viewAllLogInOptions": { + "message": "Näytä kaikki kirjautumisvaihtoehdot" + }, "viewAllLoginOptions": { "message": "Näytä kaikki kirjautumisvaihtoehdot" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Havaittiin heikko ja tietovuodosta löytynyt salasana. Sinun tulisi suojata tilisi vahvalla ja ainutlaatuisella salasanalla. Haluatko varmasti käyttää tätä salasanaa?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tarkasta esiintyykö salasanaa tunnetuissa tietovuodoissa" }, + "loggedInExclamation": { + "message": "Kirjautuminen onnistui!" + }, "important": { "message": "Tärkeää:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Suositeltava asetusmuutos" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Muista tämä laite tehdäksesi tulevista kirjautumisista helpompaa" + }, "deviceApprovalRequired": { "message": "Laitehyväksyntä vaaditaan. Valitse hyväksyntätapa alta:" }, + "deviceApprovalRequiredV2": { + "message": "Laitteen hyväksyntä vaaditaan" + }, + "selectAnApprovalOptionBelow": { + "message": "Valitse hyväksyntävaihtoehto alta" + }, "rememberThisDevice": { "message": "Muista tämä laite" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Käyttäjän sähköpostiosoite puuttuu" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiivista käyttäjän sähköpostiosoitetta ei löytynyt. Kirjaudutaan ulos." + }, "deviceTrusted": { "message": "Laitteeseen luotettu" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Kertakirjautumiselle ei löytynyt vapaita portteja." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Valtuuta" + }, + "deny": { + "message": "Estä" + }, + "sshkeyApprovalTitle": { + "message": "Vahvista SSH-avainkäyttö" + }, + "sshkeyApprovalMessageInfix": { + "message": "pyytää pääsyä" + }, + "unknownApplication": { + "message": "Sovellus" + }, + "sshKeyPasswordUnsupported": { + "message": "Salasanasuojattujen SSH-avainten tuontia ei vielä tueta" + }, + "invalidSshKey": { + "message": "SSH-avain on virheellinen" + }, + "sshKeyTypeUnsupported": { + "message": "SSH-avaintyyppiä ei ole tuettui" + }, + "importSshKeyFromClipboard": { + "message": "Tuo avain leikepöydältä" + }, + "sshKeyPasted": { + "message": "SSH-avain on tuotu" + }, "fileSavedToDevice": { "message": "Tiedosto tallennettiin laitteelle. Hallitse sitä laitteesi latauksista." + }, + "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": "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": "Muistuta myöhemmin" + }, + "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": "Ota kaksivaiheinen kirjautuminen käyttöön" + }, + "changeAcctEmail": { + "message": "Muuta tilin sähköpostiosoitetta" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index f721bb17253..1f77b85e3c5 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure na tala" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Mga Folder" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Kailangan ng premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopyahin ang password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL ng Server" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Kasaysayan ng Password" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Hilahin", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Walang mga password na ilista." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Mag-undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify para sa Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "I-unlock gamit ang Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Uri ng username" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Naipadala na ang notification sa iyong device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Mangyaring tiyakin na ang iyong vault ay naka unlock at ang parirala ng Fingerprint ay tumutugma sa iba pang aparato." }, "fingerprintPhraseHeader": { "message": "Hulmabig ng Hilik ng Dako" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Ang pag log in gamit ang device ay dapat na naka set up sa mga setting ng Bitwarden app. Kailangan mo ng ibang opsyon?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Tingnan ang lahat ng mga pagpipilian sa pag login" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mahinang password na nakilala at nakita sa data breach. Gamitin ang malakas at natatanging password upang makaproteksyon sa iyong account. Sigurado ka ba na gusto mong gamitin ang password na ito?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tingnan ang kilalang breaches ng data para sa password na ito" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Mahalaga:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 99c99150ad9..f0ab36ba636 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Note sécurisée" }, + "typeSshKey": { + "message": "Clé SSH" + }, "folders": { "message": "Dossiers" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresse" }, + "sshPrivateKey": { + "message": "Clé privée" + }, + "sshPublicKey": { + "message": "Clé publique" + }, + "sshFingerprint": { + "message": "Empreinte digitale" + }, + "sshKeyAlgorithm": { + "message": "Type de clé" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bits" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bits" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bits" + }, + "sshKeyGenerated": { + "message": "Une nouvelle clé SSH a été générée" + }, + "sshKeyWrongPassword": { + "message": "Le mot de passe saisi est incorrect." + }, + "importSshKey": { + "message": "Importer" + }, + "confirmSshKeyPassword": { + "message": "Confirmez le mot de passe" + }, + "enterSshKeyPasswordDesc": { + "message": "Entrez le mot de passe de la clé SSH." + }, + "enterSshKeyPassword": { + "message": "Entrez le mot de passe" + }, + "sshAgentUnlockRequired": { + "message": "Veuillez déverrouiller votre coffre pour approuver la demande de clé SSH." + }, + "sshAgentUnlockTimeout": { + "message": "La requête de clé SSH a expiré." + }, + "enableSshAgent": { + "message": "Activer l'agent SSH" + }, + "enableSshAgentDesc": { + "message": "Activez l'agent SSH pour signer les requêtes SSH directement depuis votre coffre Bitwarden." + }, + "enableSshAgentHelp": { + "message": "L'agent SSH est un service destiné aux développeurs qui vous permet de signer des requêtes SSH directement depuis votre coffre Bitwarden." + }, "premiumRequired": { "message": "Premium requis" }, @@ -189,6 +249,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvier" }, @@ -263,7 +337,7 @@ "message": "Générer un mot de passe" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Générer une phrase de passe" }, "type": { "message": "Type" @@ -400,8 +474,14 @@ "copyPassword": { "message": "Copier le mot de passe" }, + "regenerateSshKey": { + "message": "Régénérer la clé SSH" + }, + "copySshPrivateKey": { + "message": "Copier la clé privée SSH" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copier la phrase de passe", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -853,8 +933,14 @@ "baseUrl": { "message": "URL du serveur" }, + "authenticationTimeout": { + "message": "Délai d'authentification dépassé" + }, + "authenticationSessionTimedOut": { + "message": "La session d'authentification a expiré. Veuillez redémarrer le processus de connexion." + }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL du serveur auto-hébergé", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Historique du mot de passe" }, + "generatorHistory": { + "message": "Historique du générateur" + }, + "clearGeneratorHistoryTitle": { + "message": "Effacer l'historique du générateur" + }, + "cleargGeneratorHistoryDescription": { + "message": "Si vous continuez, toutes les entrées seront définitivement supprimées de l'historique du générateur. Êtes-vous sûr de vouloir continuer ?" + }, "clear": { "message": "Effacer", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Aucun mot de passe à afficher." }, + "clearHistory": { + "message": "Effacer l'historique" + }, + "nothingToShow": { + "message": "Rien à afficher" + }, + "nothingGeneratedRecently": { + "message": "Vous n'avez rien généré récemment" + }, "undo": { "message": "Annuler" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Vérifier pour Bitwarden." }, - "polkitConsentMessage": { - "message": "S'authentifier pour déverrouiller Bitwarden." - }, "unlockWithTouchId": { "message": "Déverrouiller avec Touch ID" }, @@ -1864,7 +1965,7 @@ "message": "Votre nouveau mot de passe principal ne répond pas aux exigences de politique de sécurité." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Obtenez des conseils, des annonces et des opportunités de recherche de la part de Bitwarden dans votre boîte de réception." }, "unsubscribe": { "message": "Se désabonner" @@ -2391,10 +2492,10 @@ "message": "Générer le nom d'utilisateur" }, "generateEmail": { - "message": "Generate email" + "message": "Générer un courriel" }, - "generatorBoundariesHint": { - "message": "La valeur doit être comprise entre $MIN$ et $MAX$", + "spinboxBoundariesHint": { + "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Utilisez $RECOMMENDED$ caractères ou plus pour générer un mot de passe fort.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilisez $RECOMMENDED$ mots ou plus pour générer une phrase de passe 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Type de nom d'utilisateur" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Une notification a été envoyée à votre appareil." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte digitale correspond à celle de l'autre appareil" + }, + "needAnotherOptionV1": { + "message": "Besoin d'une autre option ?" + }, "fingerprintMatchInfo": { "message": "Veuillez vous assurer que votre coffre est déverrouillé et que la phrase d'empreinte correspond à celle de l'autre appareil." }, "fingerprintPhraseHeader": { "message": "Phrase d'empreinte" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Vous serez notifié une fois que la demande sera approuvée" + }, "needAnotherOption": { "message": "La connexion avec l'appareil doit être configurée dans les paramètres de l'application Bitwarden. Besoin d'une autre option ?" }, + "viewAllLogInOptions": { + "message": "Afficher toutes les options de connexion" + }, "viewAllLoginOptions": { "message": "Afficher toutes les options de connexion" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mot de passe faible identifié et trouvé dans une brèche de données. Utilisez un mot de passe robuste et unique pour protéger votre compte. Êtes-vous sûr de vouloir utiliser ce mot de passe ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Vérifier les brèches de données connues pour ce mot de passe" }, + "loggedInExclamation": { + "message": "Connecté !" + }, "important": { "message": "Important :" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Une mise à jour des paramètres est recommandée" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Mémorisez cet appareil pour faciliter les futures connexions" + }, "deviceApprovalRequired": { "message": "L'approbation de l'appareil est requise. Sélectionnez une option d'approbation ci-dessous:" }, + "deviceApprovalRequiredV2": { + "message": "Autorisation de l'appareil requise" + }, + "selectAnApprovalOptionBelow": { + "message": "Sélectionnez une option d'approbation ci-dessous" + }, "rememberThisDevice": { "message": "Se souvenir de cet appareil" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "E-mail de l'utilisateur manquant" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Courriel utilisateur actif introuvable. Déconnexion en cours." + }, "deviceTrusted": { "message": "Appareil de confiance" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Autoriser" + }, + "deny": { + "message": "Refuser" + }, + "sshkeyApprovalTitle": { + "message": "Confirmer l'utilisation de la clé SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "demande l'accès à" + }, + "unknownApplication": { + "message": "Une application" + }, + "sshKeyPasswordUnsupported": { + "message": "L'importation de clés SSH protégées par mot de passe n'est pas encore prise en charge" + }, + "invalidSshKey": { + "message": "La clé SSH est invalide" + }, + "sshKeyTypeUnsupported": { + "message": "Le type de clé SSH n'est pas pris en charge" + }, + "importSshKeyFromClipboard": { + "message": "Importer une clé depuis le presse-papiers" + }, + "sshKeyPasted": { + "message": "Clé SSH importée avec succès" + }, "fileSavedToDevice": { "message": "Fichier enregistré sur l'appareil. Gérez à partir des téléchargements de votre appareil." + }, + "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" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 811885b464d..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index ead5dc7384c..df61cefe73d 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "פתק מאובטח" }, + "typeSshKey": { + "message": "מפתח SSH" + }, "folders": { "message": "תיקיות" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "ברוך שובך" }, "moveToOrgDesc": { "message": "בחר ארגון שאליו תרצה להעביר פריט זה. העברה לארגון מעבירה את הבעלות על הפריט לאותו ארגון. לאחר העברת פריט זה לא תהיה יותר הבעלים הישיר." @@ -177,6 +180,63 @@ "address": { "message": "כתובת" }, + "sshPrivateKey": { + "message": "מפתח פרטי" + }, + "sshPublicKey": { + "message": "מפתח ציבורי" + }, + "sshFingerprint": { + "message": "טביעת אצבע" + }, + "sshKeyAlgorithm": { + "message": "סוג מפתח" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA‏ 2048 סיביות" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA‏ 3072 סיביות" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA‏ 4096 סיביות" + }, + "sshKeyGenerated": { + "message": "נוצר מפתח SSH חדש" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "הפעלת סוכן SSH" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "נדרש חשבון פרימיום" }, @@ -189,6 +249,20 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ינואר" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "העתק סיסמה" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "כתובת שרת" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "היסטוריית סיסמאות" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "נקה", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "אין סיסמאות להצגה ברשימה." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "בטל" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "אימות עבור Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "שחרור נעילה עם Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "סוג שם משתמש" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "התראה נשלחה למכשירך." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "וודא שהכספת שלך לא נעולה ושם המזהה שלך מתאים למכשיר השני." }, "fingerprintPhraseHeader": { "message": "שם מזהה" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "התחברות עם מכשיר צריכה להיות מוגדרת בהגדרות האפליקציה. זקוק/ה לאפשרות נוספת?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "צפייה בכל אפשרות ההתחברות" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "חשוב:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 421c2665d4f..723ea0f6992 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 47bb80f1542..109a1abff21 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sigurna bilješka" }, + "typeSshKey": { + "message": "SSH ključ" + }, "folders": { "message": "Mape" }, @@ -61,13 +64,13 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Dobro došli natrag" }, "moveToOrgDesc": { "message": "Odaberi organizaciju u koju želiš premjestiti ovu stavku. Premještanje prenosi vlasništvo stavke na organizaciju. Nakon premještanja više nećeš biti izravni vlasnik ove stavke." }, "attachments": { - "message": "Privitci" + "message": "Privíci" }, "viewItem": { "message": "Prikaz stavke" @@ -177,6 +180,63 @@ "address": { "message": "Adresa" }, + "sshPrivateKey": { + "message": "Privatni ključ" + }, + "sshPublicKey": { + "message": "Javni ključ" + }, + "sshFingerprint": { + "message": "Otisak prsta" + }, + "sshKeyAlgorithm": { + "message": "Vrsta ključa" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Generiran je novi SSH ključ" + }, + "sshKeyWrongPassword": { + "message": "Unesena lozinka nije ispravna." + }, + "importSshKey": { + "message": "Uvoz" + }, + "confirmSshKeyPassword": { + "message": "Potvrdi lozinku" + }, + "enterSshKeyPasswordDesc": { + "message": "Unesi lozinku za SSH ključ." + }, + "enterSshKeyPassword": { + "message": "Unesi lozinku" + }, + "sshAgentUnlockRequired": { + "message": "Otključaj svoj trezor za odobravanje zahtjeva za SSH ključ." + }, + "sshAgentUnlockTimeout": { + "message": "Zahtjev za SSH ključ istekao." + }, + "enableSshAgent": { + "message": "Uključi SSH agent" + }, + "enableSshAgentDesc": { + "message": "Uključi SSH agent za potpis SSH zahtjeva direktno iz tvog Bitwarden trezora." + }, + "enableSshAgentHelp": { + "message": "SSH agent je servis namijenjen developerima koji omogućuje potpisivanje SSH zahtjeva izravno iz tvojeg Bitwarden trezora." + }, "premiumRequired": { "message": "Potrebno Premium članstvo" }, @@ -189,6 +249,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "siječanj" }, @@ -263,7 +337,7 @@ "message": "Generiraj lozinku" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generiraj fraznu lozinku" }, "type": { "message": "Vrsta" @@ -400,8 +474,14 @@ "copyPassword": { "message": "Kopiraj lozinku" }, + "regenerateSshKey": { + "message": "Regeneriraj SSH ključ" + }, + "copySshPrivateKey": { + "message": "Kopiraj privatni SSH ključ" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Kopiraj fraznu lozinku", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -464,7 +544,7 @@ "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { - "message": "!@#$%^&*", + "message": "! @ # $ % ^ & *", "description": "Label for the password generator special characters checkbox" }, "numWords": { @@ -558,7 +638,7 @@ "message": "Stvori račun" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novi u Bitwardenu?" }, "setAStrongPassword": { "message": "Postavi jaku lozinku" @@ -570,16 +650,16 @@ "message": "Prijavi se" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Prijavi se u Bitwarden" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prijava pristupnim ključem" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Prijava uređajem" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Jedinstvena prijava (SSO)" }, "submit": { "message": "Pošalji" @@ -628,7 +708,7 @@ "message": "Pridruži se organizaciji" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Pidruži se $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -743,10 +823,10 @@ "message": "Nastavi" }, "enterVerificationCodeApp": { - "message": "Unesi 6-znamenkasti kontrolni kôd iz autentifikatorske aplikacije." + "message": "Unesi 6-znamenkasti kôd za provjeru iz autentifikatorske aplikacije." }, "enterVerificationCodeEmail": { - "message": "Unesi 6-znamenkasti kontrolni kôd poslan e-poštom na $EMAIL$.", + "message": "Unesi 6-znamenkasti kôd za provjeru poslan e-poštom na $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -853,8 +933,14 @@ "baseUrl": { "message": "URL poslužitelja" }, + "authenticationTimeout": { + "message": "Istek vremena za autentifikaciju" + }, + "authenticationSessionTimedOut": { + "message": "Sesija za autentifikaciju je istekla. Ponovi proces prijave." + }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL vlastitog poslužitelja", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Kopiraj e-poštu" }, "copySecurityCode": { "message": "Kopiraj kontrolni broj", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Povijest" }, + "generatorHistory": { + "message": "Povijest generatora" + }, + "clearGeneratorHistoryTitle": { + "message": "Očisti povijest generatora" + }, + "cleargGeneratorHistoryDescription": { + "message": "Cijela povijest generatora biti će trajno izbirsana. Sigurno želiš nastaviti?" + }, "clear": { "message": "Očisti", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nema lozinki na popisu." }, + "clearHistory": { + "message": "Očisti povijest" + }, + "nothingToShow": { + "message": "Ništa za prikazati" + }, + "nothingGeneratedRecently": { + "message": "Ništa nije generirano" + }, "undo": { "message": "Poništi" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Otključaj trezor." }, - "polkitConsentMessage": { - "message": "Otključaj Bitwarden autentifikacijom." - }, "unlockWithTouchId": { "message": "Otključaj koristeći Touch ID" }, @@ -1684,10 +1785,10 @@ "message": "Brisanje računa je TRAJNO i NEPOVRATNO i naknadno ga nije moguće vratiti." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Nije moguće izbrisati račun" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Ova radnja ne može se završiti jer je tvoj račun u vlasništvu organizacije. Za detalje, kontaktiraj administratora organizacije." }, "accountDeleted": { "message": "Račun izbrisan" @@ -1800,7 +1901,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "od $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2391,10 +2492,10 @@ "message": "Generiraj korisničko ime" }, "generateEmail": { - "message": "Generate email" + "message": "Generiraj e-poštu" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Vrijednost mora biti u rasponu $MIN$ - $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Koristi $RECOMMENDED$ i više znakova za generiranje jake lozinke.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Koristi $RECOMMENDED$ i više riječi za generiranje jake frazne lozinke.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tip korisničkog imena" }, @@ -2451,11 +2572,11 @@ "message": "Generiraj pseudonim e-pošte s vanjskom uslugom prosljeđivanja." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domena e-pošte", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Odaberi domenu koju podržava odabrani servis", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Obavijest je poslana na tvoj uređaj." }, + "aNotificationWasSentToYourDevice": { + "message": "Obavijest je poslana na tvoj uređaj" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem" + }, + "needAnotherOptionV1": { + "message": "Trebaš drugu opciju?" + }, "fingerprintMatchInfo": { "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem." }, "fingerprintPhraseHeader": { "message": "Jedinstvena fraza" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Dobiti ćeš obavijest kada je tvoj zahtjev odobren" + }, "needAnotherOption": { "message": "Prijava uređajem mora biti namještena u postavka Bitwarden mobilne aplikacije. Trebaš drugu opciju?" }, + "viewAllLogInOptions": { + "message": "Pogledaj sve mogućnosti prijave" + }, "viewAllLoginOptions": { "message": "Pogledaj sve mogućnosti prijave" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slaba lozinka je nađena među ukradenima tijekom krađa podataka. Za zaštitu svog računa koristi jaku i jedinstvenu lozinku. Želiš li svejedno korisiti slabu, ukradenu lozinku?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Provjeri je li lozinka ukradena prilikom krađe podataka" }, + "loggedInExclamation": { + "message": "Prijava uspješna!" + }, "important": { "message": "Važno:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Preporučeno ažuriranje postavki" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamti ovaj uređaj kako bi buduće prijave bile brže" + }, "deviceApprovalRequired": { "message": "Potrebno je odobriti uređaj. Odaberi metodu odobravanja:" }, + "deviceApprovalRequiredV2": { + "message": "Potrebno odobrenje uređaja" + }, + "selectAnApprovalOptionBelow": { + "message": "Odaberi opciju odobrenja" + }, "rememberThisDevice": { "message": "Zapamti ovaj uređaj" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Nedostaje e-pošta korisnika" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Nije pronađena e-pošta aktivnog korisnika. Odjava u tijeku..." + }, "deviceTrusted": { "message": "Uređaj pouzdan" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Nisu nađeni slobodni portovi za SSO prijavu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Autoriziraj" + }, + "deny": { + "message": "Odbij" + }, + "sshkeyApprovalTitle": { + "message": "Potvrdi korištenje SSH ključa" + }, + "sshkeyApprovalMessageInfix": { + "message": "traži pristup za" + }, + "unknownApplication": { + "message": "Aplikacija" + }, + "sshKeyPasswordUnsupported": { + "message": "Uvoz SSH ključeva zaštićenih lozinkom još nije podržan" + }, + "invalidSshKey": { + "message": "SSH ključ nije valjan" + }, + "sshKeyTypeUnsupported": { + "message": "Tip SSH ključa nije podržan" + }, + "importSshKeyFromClipboard": { + "message": "Uvezi ključ iz međuspremnika" + }, + "sshKeyPasted": { + "message": "SSH ključ uspješno uvezen" + }, "fileSavedToDevice": { "message": "Datoteka spremljena na uređaj. Upravljaj u preuzimanjima svog uređaja." + }, + "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" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index cc147ae5ead..3716e1e67a9 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Biztonságos jegyzet" }, + "typeSshKey": { + "message": "SSH kulcs" + }, "folders": { "message": "Mappák" }, @@ -177,6 +180,63 @@ "address": { "message": "Postai cím" }, + "sshPrivateKey": { + "message": "Személyes kulcs" + }, + "sshPublicKey": { + "message": "Nyilvános kulcs" + }, + "sshFingerprint": { + "message": "Ujjlenyomat" + }, + "sshKeyAlgorithm": { + "message": "Kulcs típusa" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Új SSH-kulcs jött létre." + }, + "sshKeyWrongPassword": { + "message": "A megadott jelszó helytelen." + }, + "importSshKey": { + "message": "Importálás" + }, + "confirmSshKeyPassword": { + "message": "Jelszó megerősítése" + }, + "enterSshKeyPasswordDesc": { + "message": "Adjuk meg az SSH kulcs jelszót." + }, + "enterSshKeyPassword": { + "message": "Jelszó megadása" + }, + "sshAgentUnlockRequired": { + "message": "Az SSH-kulcskérés jóváhagyásához oldjuk fel a széfet." + }, + "sshAgentUnlockTimeout": { + "message": "Az SSH-kulcs kérése időtúllépést okozott." + }, + "enableSshAgent": { + "message": "SSH ügynök engedélyezése" + }, + "enableSshAgentDesc": { + "message": "Engedélyezzük az SSH ügynök számára az SSH kérelmek aláírását közvetlenül a Bitwarden széfből." + }, + "enableSshAgentHelp": { + "message": "Az SSH ügynök egy fejlesztőknek szánt szolgáltatás, amely lehetővé teszi az SSH kérések közvetlenül a Bitwarden széfből történő aláírását." + }, "premiumRequired": { "message": "Prémium szükséges" }, @@ -189,6 +249,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérés siker", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "január" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Jelszó másolása" }, + "regenerateSshKey": { + "message": "SSH kulcs újragenerálása" + }, + "copySshPrivateKey": { + "message": "SSH személyes kulcs másolása" + }, "copyPassphrase": { "message": "Jelmondat másolása", "description": "Copy passphrase to clipboard" @@ -573,7 +653,7 @@ "message": "Bejelentkezés a Bitwardenbe" }, "logInWithPasskey": { - "message": "Bejelentkezés azonosító kulcssal" + "message": "Bejelentkezés hozzáférési kulccsal" }, "loginWithDevice": { "message": "Bejelentkezés eszközzel" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Szerver webcím" }, + "authenticationTimeout": { + "message": "Hitelesítési időkifutás" + }, + "authenticationSessionTimedOut": { + "message": "A hitelesítési munkamenet időkifutással leállt. Indítsuk újra a bejelentkezési folyamatot." + }, "selfHostBaseUrl": { "message": "Saját üzemeltetésű szerver webcím", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Jelszó előzmények" }, + "generatorHistory": { + "message": "Generátor előzmények" + }, + "clearGeneratorHistoryTitle": { + "message": "Generátor előzmények kiürítése" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ha folytatjuk, az összes bejegyzés véglegesen törlődik a generátor előzményeiből. Biztosan folytatjuk?" + }, "clear": { "message": "Kiürítés", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nincsenek listázható jelszavak." }, + "clearHistory": { + "message": "Előzmények törlése" + }, + "nothingToShow": { + "message": "Nincs megjeleníthető elem" + }, + "nothingGeneratedRecently": { + "message": "Mostanában nem lett semmi generálva." + }, "undo": { "message": "Visszavonás" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bitwarden ellenőrzés." }, - "polkitConsentMessage": { - "message": "Hitelesítés a Bitwarden feloldásához." - }, "unlockWithTouchId": { "message": "Feloldás Touch ID segítségével" }, @@ -2393,7 +2494,7 @@ "generateEmail": { "message": "Email generálása" }, - "generatorBoundariesHint": { + "spinboxBoundariesHint": { "message": "Az érték legyen $MIN$ és $MAX$ között.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Használjunk $RECOMMENDED$ vagy több karaktert egy erős jelszó előállításához.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": "Használjunk $RECOMMENDED$ vagy több szót erős jelmondat generálásához.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Felhasználónév típusa" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, + "aNotificationWasSentToYourDevice": { + "message": "Egy értesítés lett elküldve az eszközre." + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Ellenőrizzük, hogy a széf feloldásra került és az ujjlenyomat kifejezés egyezik a másik eszközön levővel." + }, + "needAnotherOptionV1": { + "message": "Másik opció szükséges?" + }, "fingerprintMatchInfo": { "message": "Ellenőrizzük, hogy a széf feloldásra került és az Ujjlenyomat kifejezés egyezik a másik eszközön levővel." }, "fingerprintPhraseHeader": { "message": "Ujjlenyomat kifejezés" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "A kérelem jóváhagyása után értesítés érkezik." + }, "needAnotherOption": { "message": "Az eszközzel történő bejelentkezést a Biwarden mobilalkalmazás beállításaiban kell beállítani. Másik opcióra van szükség?" }, + "viewAllLogInOptions": { + "message": "Összes bejelentkezési opció megtekintése" + }, "viewAllLoginOptions": { "message": "Összes bejelentkezési opció megtekintése" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" }, + "useThisPassword": { + "message": "Jelszó használata" + }, + "useThisUsername": { + "message": "Felhasználónév használata" + }, "checkForBreaches": { "message": "Az ehhez a jelszóhoz tartozó ismert adatvédelmi incidensek ellenőrzése" }, + "loggedInExclamation": { + "message": "Megtörtént a bejelentkezés." + }, "important": { "message": "Fontos:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Ajánlott beállítások frissítése" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Emlékezés az eszközre, hogy zökkenőmentes legyen a jövőbeni bejelentkezés" + }, "deviceApprovalRequired": { "message": "Az eszköz jóváhagyása szükséges. Válasszunk egy jóváhagyási lehetőséget lentebb:" }, + "deviceApprovalRequiredV2": { + "message": "Eszköz jóváhagyás szükséges" + }, + "selectAnApprovalOptionBelow": { + "message": "Válasszunk lentebb egy jóváhagyási lehetőséget." + }, "rememberThisDevice": { "message": "Eszköz megjegyzése" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "A felhasználói email cím hiányzik." }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Az aktív felhasználói email cím nem található. Kijelentkeztetés történik." + }, "deviceTrusted": { "message": "Az eszköz megbízható." }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Nem található szabad port az sso bejelentkezéshez." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, + "authorize": { + "message": "Hitelesítés" + }, + "deny": { + "message": "Elutasítás" + }, + "sshkeyApprovalTitle": { + "message": "SSH kulcs használat megerősítése" + }, + "sshkeyApprovalMessageInfix": { + "message": "hozzáférést kér:" + }, + "unknownApplication": { + "message": "Egy alkalmazás" + }, + "sshKeyPasswordUnsupported": { + "message": "A jelszóval védett SSH kulcsok importálása még nem támogatott." + }, + "invalidSshKey": { + "message": "Az SSH kulcs érvénytelen." + }, + "sshKeyTypeUnsupported": { + "message": "Az SSH kulcstípus nem támogatott." + }, + "importSshKeyFromClipboard": { + "message": "Kulcs importálása vágólapból" + }, + "sshKeyPasted": { + "message": "Az SSH kulcs sikeresen importálásra került." + }, "fileSavedToDevice": { "message": "A fájl mentésre került az eszközre. Kezeljük az eszközről a letöltéseket." + }, + "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" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index a5aeb3e4f40..d4bcded0ee8 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Catatan yang aman" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folder" }, @@ -177,6 +180,63 @@ "address": { "message": "Alamat" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Memerlukan Keanggotaan Premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Salin Kata Sandi" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL Server" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Riwayat Kata Sandi" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Bersihkan", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Tidak ada sandi yang dapat dicantumkan." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Urungkan" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verifikasi untuk Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Buka kunci dengan Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Jenis nama pengguna" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Periksa pelanggaran data yang diketahui untuk kata sandi ini" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Penting:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 92732544785..c85127fce20 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Nota sicura" }, + "typeSshKey": { + "message": "Chiave SSH" + }, "folders": { "message": "Cartelle" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Bentornato" }, "moveToOrgDesc": { "message": "Scegli un'organizzazione in cui vuoi spostare questo elemento. Spostarlo in un'organizzazione trasferisce la proprietà dell'elemento all'organizzazione. Una volta spostato, non sarai più il proprietario diretto di questo elemento." @@ -177,6 +180,63 @@ "address": { "message": "Indirizzo" }, + "sshPrivateKey": { + "message": "Chiave privata" + }, + "sshPublicKey": { + "message": "Chiave pubblica" + }, + "sshFingerprint": { + "message": "Impronta digitale" + }, + "sshKeyAlgorithm": { + "message": "Tipo di chiave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA a 2048 bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA a 3072 bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA a 4096 bit" + }, + "sshKeyGenerated": { + "message": "È stata generata una nuova chiave SSH" + }, + "sshKeyWrongPassword": { + "message": "La password inserita non è corretta." + }, + "importSshKey": { + "message": "Importa" + }, + "confirmSshKeyPassword": { + "message": "Conferma password" + }, + "enterSshKeyPasswordDesc": { + "message": "Inserisci la password per la chiave SSH." + }, + "enterSshKeyPassword": { + "message": "Inserisci password" + }, + "sshAgentUnlockRequired": { + "message": "Sbloccare la cassaforte per approvare la richiesta di chiave SSH." + }, + "sshAgentUnlockTimeout": { + "message": "Richiesta chiave SSH scaduta." + }, + "enableSshAgent": { + "message": "Abilita agente SSH" + }, + "enableSshAgentDesc": { + "message": "Abilita l'agente SSH per firmare le richieste SSH direttamente dalla tua cassaforte Bitwarden." + }, + "enableSshAgentHelp": { + "message": "L'agente SSH è un servizio rivolto agli sviluppatori che consente di firmare le richieste SSH direttamente dalla tua cassaforte Bitwarden." + }, "premiumRequired": { "message": "Premium necessario" }, @@ -189,6 +249,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gennaio" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copia password" }, + "regenerateSshKey": { + "message": "Rigenera la chiave SSH" + }, + "copySshPrivateKey": { + "message": "Copia chiave privata SSH" + }, "copyPassphrase": { "message": "Copia passphrase", "description": "Copy passphrase to clipboard" @@ -558,7 +638,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nuovo in Bitwarden?" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -570,7 +650,7 @@ "message": "Accedi" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Accedi a Bitwarden" }, "logInWithPasskey": { "message": "Accedi con passkey" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL del server" }, + "authenticationTimeout": { + "message": "Timeout autenticazione" + }, + "authenticationSessionTimedOut": { + "message": "La sessione di autenticazione è scaduta. Accedi di nuovo." + }, "selfHostBaseUrl": { "message": "URL server autogestito", "description": "Label for field requesting a self-hosted integration service URL" @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copia e-mail" + "message": "Copia email" }, "copySecurityCode": { "message": "Copia codice di sicurezza", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Cronologia delle password" }, + "generatorHistory": { + "message": "Cronologia generatore" + }, + "clearGeneratorHistoryTitle": { + "message": "Cancella cronologia generatore" + }, + "cleargGeneratorHistoryDescription": { + "message": "Se continui, tutte le voci verranno eliminate definitivamente dalla cronologia del generatore. Vuoi continuare?" + }, "clear": { "message": "Cancella", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Non ci sono password da mostrare." }, + "clearHistory": { + "message": "Cancella cronologia" + }, + "nothingToShow": { + "message": "Niente da mostrare" + }, + "nothingGeneratedRecently": { + "message": "Non hai generato niente di recente" + }, "undo": { "message": "Annulla" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verifica per Bitwarden." }, - "polkitConsentMessage": { - "message": "Autenticazione per sbloccare Bitwarden." - }, "unlockWithTouchId": { "message": "Sblocca con Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Genera e-mail" }, - "generatorBoundariesHint": { - "message": "Il valore deve essere compreso tra $MIN$ e $MAX$", + "spinboxBoundariesHint": { + "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Usa $RECOMMENDED$ caratteri o più per generare una password forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Usa $RECOMMENDED$ parole o più per generare una passphrase 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo di nome utente" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Una notifica è stata inviata al tuo dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Assicurati che il tuo account sia sbloccato e che la frase dell'impronta digitale corrisponda nell'altro dispositivo" + }, + "needAnotherOptionV1": { + "message": "Bisogno di un'altra opzione?" + }, "fingerprintMatchInfo": { "message": "Assicurati che la tua cassaforte sia sbloccata e che la frase impronta corrisponda sull'altro dispositivo." }, "fingerprintPhraseHeader": { "message": "Frase impronta" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Sarai notificato una volta che la richiesta sarà approvata" + }, "needAnotherOption": { "message": "L'accesso con dispositivo deve essere abilitato nelle impostazioni dell'app Bitwarden. Ti serve un'altra opzione?" }, + "viewAllLogInOptions": { + "message": "Visualizza tutte le opzioni di accesso" + }, "viewAllLoginOptions": { "message": "Visualizza tutte le opzioni di accesso" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Password debole e trovata in una violazione dei dati. Usa una password forte e unica per proteggere il tuo account. Sei sicuro di voler usare questa password?" }, + "useThisPassword": { + "message": "Usa questa parola d'accesso" + }, + "useThisUsername": { + "message": "Usa questo nome utente" + }, "checkForBreaches": { "message": "Controlla se la tua password è presente in una violazione dei dati" }, + "loggedInExclamation": { + "message": "Accesso effettuato!" + }, "important": { "message": "Importante:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Aggiornamento delle impostazioni consigliato" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Ricorda questo dispositivo per rendere immediati i futuri accessi" + }, "deviceApprovalRequired": { "message": "Approvazione del dispositivo obbligatoria. Seleziona un'opzione di approvazione:" }, + "deviceApprovalRequiredV2": { + "message": "Approvazione dispositivo richiesta" + }, + "selectAnApprovalOptionBelow": { + "message": "Seleziona un'opzione di approvazione sotto" + }, "rememberThisDevice": { "message": "Ricorda questo dispositivo" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Email utente mancante" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Email utente attiva non trovata. Logout in corso." + }, "deviceTrusted": { "message": "Dispositivo fidato" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Non è stato possibile trovare nessuna porta libera per il login Sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, + "authorize": { + "message": "Autorizza" + }, + "deny": { + "message": "Nega" + }, + "sshkeyApprovalTitle": { + "message": "Conferma l'uso della chiave SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "richiede l'accesso a" + }, + "unknownApplication": { + "message": "Un'applicazione" + }, + "sshKeyPasswordUnsupported": { + "message": "L'importazione di chiavi SSH protette da password non è ancora supportata" + }, + "invalidSshKey": { + "message": "La chiave SSH non è valida" + }, + "sshKeyTypeUnsupported": { + "message": "Il tipo di chiave SSH non è supportato" + }, + "importSshKeyFromClipboard": { + "message": "Importa chiave dagli Appunti" + }, + "sshKeyPasted": { + "message": "Chiave SSH importata correttamente" + }, "fileSavedToDevice": { "message": "File salvato sul dispositivo. Gestisci dai download del dispositivo." + }, + "importantNotice": { + "message": "Notifica importante" + }, + "setupTwoStepLogin": { + "message": "Imposta accesso in due passaggi" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden invierà un codice all'e-mail 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": "Hai accesso affidabile alla tua e-mail, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, non ce l'ho" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Sì, posso accedere in modo affidabile alla mia e-mail" + }, + "turnOnTwoStepLogin": { + "message": "Attiva accesso in due passaggi" + }, + "changeAcctEmail": { + "message": "Cambia l'e-mail dell'account" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 8024c41d83f..9ce8f6a3ea7 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "セキュアメモ" }, + "typeSshKey": { + "message": "SSH キー" + }, "folders": { "message": "フォルダー" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "ようこそ" }, "moveToOrgDesc": { "message": "このアイテムを移動する組織を選択してください。組織に移動すると、アイテムの所有権がその組織に移行します。 このアイテムが移動された後、あなたはこのアイテムの直接の所有者にはなりません。" @@ -177,6 +180,63 @@ "address": { "message": "住所" }, + "sshPrivateKey": { + "message": "秘密鍵" + }, + "sshPublicKey": { + "message": "公開鍵" + }, + "sshFingerprint": { + "message": "フィンガープリント" + }, + "sshKeyAlgorithm": { + "message": "キーの種類" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "新しい SSH 鍵が生成されました" + }, + "sshKeyWrongPassword": { + "message": "入力されたパスワードが間違っています。" + }, + "importSshKey": { + "message": "インポート" + }, + "confirmSshKeyPassword": { + "message": "パスワードを確認" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH キーのパスワードを入力します。" + }, + "enterSshKeyPassword": { + "message": "パスワードを入力" + }, + "sshAgentUnlockRequired": { + "message": "SSH キーリクエストを承認するには、保管庫のロックを解除してください。" + }, + "sshAgentUnlockTimeout": { + "message": "SSH キーの要求がタイムアウトしました。" + }, + "enableSshAgent": { + "message": "SSH エージェントを有効にする" + }, + "enableSshAgentDesc": { + "message": "Bitwarden 保管庫から直接 SSH 要求に署名するために SSH エージェントを有効にします。" + }, + "enableSshAgentHelp": { + "message": "SSH エージェントとは、Bitwarden 保管庫から直接 SSH リクエストに署名できる、開発者を対象としたサービスです。" + }, "premiumRequired": { "message": "プレミアム会員専用" }, @@ -189,6 +249,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1月" }, @@ -263,7 +337,7 @@ "message": "パスワードの自動生成" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "パスフレーズを生成" }, "type": { "message": "タイプ" @@ -400,8 +474,14 @@ "copyPassword": { "message": "パスワードのコピー" }, + "regenerateSshKey": { + "message": "SSH キーを再生成" + }, + "copySshPrivateKey": { + "message": "SSH 秘密鍵をコピー" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "パスフレーズをコピー", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -558,7 +638,7 @@ "message": "アカウントの作成" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden は初めてですか?" }, "setAStrongPassword": { "message": "強力なパスワードを設定する" @@ -570,16 +650,16 @@ "message": "ログイン" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden にログイン" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "パスキーでログイン" }, "loginWithDevice": { - "message": "Log in with device" + "message": "デバイスでログイン" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "シングルサインオンを使用する" }, "submit": { "message": "送信" @@ -628,7 +708,7 @@ "message": "組織に参加" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "$ORGANIZATIONNAME$ に参加", "placeholders": { "organizationName": { "content": "$1", @@ -853,8 +933,14 @@ "baseUrl": { "message": "サーバー URL" }, + "authenticationTimeout": { + "message": "認証のタイムアウト" + }, + "authenticationSessionTimedOut": { + "message": "認証セッションの有効期限が切れました。ログイン操作を最初からやり直してください。" + }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自己ホスト型サーバーの URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "メールアドレスをコピー" }, "copySecurityCode": { "message": "セキュリティコードのコピー", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "パスワードの履歴" }, + "generatorHistory": { + "message": "生成履歴" + }, + "clearGeneratorHistoryTitle": { + "message": "生成履歴を消去" + }, + "cleargGeneratorHistoryDescription": { + "message": "続行すると、すべてのエントリは生成履歴から完全に削除されます。続行してもよろしいですか?" + }, "clear": { "message": "消去する", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "表示するパスワードがありません" }, + "clearHistory": { + "message": "履歴を消去" + }, + "nothingToShow": { + "message": "表示するものがありません" + }, + "nothingGeneratedRecently": { + "message": "最近生成したものはありません" + }, "undo": { "message": "元に戻す" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bitwarden の認証を行います。" }, - "polkitConsentMessage": { - "message": "認証して Bitwarden のロックを解除します。" - }, "unlockWithTouchId": { "message": "Touch ID でロック解除" }, @@ -1684,10 +1785,10 @@ "message": "アカウントを恒久的に削除します。元に戻すことはできません。" }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "アカウントを削除できません" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "このアカウントは組織が所有しているため、操作を完了できません。詳しくは組織の管理者へご確認ください。" }, "accountDeleted": { "message": "アカウントが削除されました" @@ -2391,10 +2492,10 @@ "message": "ユーザー名を生成" }, "generateEmail": { - "message": "Generate email" + "message": "メールアドレスを生成" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "ユーザー名の種類" }, @@ -2451,11 +2572,11 @@ "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": { @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "デバイスに通知を送信しました。" }, + "aNotificationWasSentToYourDevice": { + "message": "お使いのデバイスに通知が送信されました" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末と一致していることを確認してください" + }, + "needAnotherOptionV1": { + "message": "別の選択肢が必要ですか?" + }, "fingerprintMatchInfo": { "message": "保管庫がロックされていることと、パスフレーズが他のデバイスと一致していることを確認してください。" }, "fingerprintPhraseHeader": { "message": "パスフレーズ" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "リクエストが承認されると通知されます" + }, "needAnotherOption": { "message": "Bitwarden アプリの設定でデバイスでログインする必要があります。別のオプションが必要ですか?" }, + "viewAllLogInOptions": { + "message": "すべてのログインオプションを表示" + }, "viewAllLoginOptions": { "message": "すべてのログインオプションを表示" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "入力されたパスワードは脆弱かつすでに流出済みです。アカウントを守るためより強力で一意なパスワードを使用してください。本当にこの脆弱なパスワードを使用しますか?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "このパスワードの既知のデータ流出を確認" }, + "loggedInExclamation": { + "message": "ログインしました!" + }, "important": { "message": "重要" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "設定の更新を推奨" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "このデバイスを記憶して今後のログインをシームレスにする" + }, "deviceApprovalRequired": { "message": "デバイスの承認が必要です。以下から承認オプションを選択してください:" }, + "deviceApprovalRequiredV2": { + "message": "デバイスの承認が必要です" + }, + "selectAnApprovalOptionBelow": { + "message": "以下の承認オプションを選択してください" + }, "rememberThisDevice": { "message": "このデバイスを記憶する" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "ユーザーのメールアドレスがありません" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "アクティブなユーザーメールアドレスが見つかりません。ログアウトします。" + }, "deviceTrusted": { "message": "信頼されたデバイス" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "SSO ログインのための空きポートが見つかりませんでした。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "認可" + }, + "deny": { + "message": "拒否" + }, + "sshkeyApprovalTitle": { + "message": "SSH 鍵の使用を確認します" + }, + "sshkeyApprovalMessageInfix": { + "message": "がアクセスを要求しています: " + }, + "unknownApplication": { + "message": "アプリ" + }, + "sshKeyPasswordUnsupported": { + "message": "パスワードで保護された SSH キーのインポートはまだサポートされていません" + }, + "invalidSshKey": { + "message": "SSH キーが無効です" + }, + "sshKeyTypeUnsupported": { + "message": "サポートされていない種類の SSH キーです" + }, + "importSshKeyFromClipboard": { + "message": "クリップボードからキーをインポート" + }, + "sshKeyPasted": { + "message": "SSH キーのインポートに成功しました" + }, "fileSavedToDevice": { "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": "アカウントのメールアドレスを変更する" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index fe2d8b280a1..bf434a6c29f 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "საქაღალდეები" }, @@ -177,6 +180,63 @@ "address": { "message": "მისამართი" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "იანვარი" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "პაროლის კოპირება" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "სერვერის URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "გაწმენდა", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "დაბრუნება" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 811885b464d..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index e4e1f85f2b7..f3401477f4a 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "ಸುರಕ್ಷಿತ ಟಿಪ್ಪಣಿ" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "ಫೋಲ್ಡರ್‌ಗಳು" }, @@ -177,6 +180,63 @@ "address": { "message": "ವಿಳಾಸ" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "ಪ್ರೀಮಿಯಂ ಅಗತ್ಯವಿದೆ" }, @@ -189,6 +249,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ಜನವರಿ" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "ಪಾಸ್ವರ್ಡ್ ನಕಲಿಸಿ" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "ಸರ್ವರ್ URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "ಪಾಸ್ವರ್ಡ್ ಇತಿಹಾಸ" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "ಕ್ಲಿಯರ್‌", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "ಪಟ್ಟಿ ಮಾಡಲು ಯಾವುದೇ ಪಾಸ್ವರ್ಡ್ಗಳು ಇಲ್ಲ." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "ರದ್ದುಮಾಡು" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "ಬಿಟ್‌ವಾರ್ಡೆನ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "ಟಚ್ ಐಡಿ ಯೊಂದಿಗೆ ಅನ್ಲಾಕ್ ಮಾಡಿ" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index f0eb10fa2a1..1aafdc4bc6d 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "보안 메모" }, + "typeSshKey": { + "message": "SSH 키" + }, "folders": { "message": "폴더" }, @@ -177,6 +180,63 @@ "address": { "message": "주소" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "프리미엄 멤버십 필요" }, @@ -189,6 +249,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1월" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "비밀번호 복사" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "서버 URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "비밀번호 변경 기록" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "삭제", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "비밀번호가 없습니다." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "실행 취소" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bitwarden에서 인증을 요청합니다." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Touch ID를 사용하여 잠금 해제" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "아이디 유형" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "기기에 알림이 전송되었습니다." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "보관함이 잠금 해제되어있고 지문 구절이 다른 기기와 일치하는지 확인하세요." }, "fingerprintPhraseHeader": { "message": "지문 구절" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "기기로 로그인하려면 Bitwarden 앱 설정에서 설정해야 합니다. 다른 방식이 필요하신가요?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "모든 로그인 방식 보기" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "중요:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -2838,7 +2995,7 @@ "message": "Input is required." }, "required": { - "message": "required" + "message": "필수" }, "search": { "message": "Search" @@ -2908,7 +3065,7 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- 선택 --" }, "multiSelectPlaceholder": { "message": "-- Type to filter --" @@ -2953,14 +3110,14 @@ "message": "Alias domain" }, "importData": { - "message": "Import data", + "message": "데이터 가져오기", "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { "message": "Import error" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "가져오려고 하는 데이터에 문제가 있습니다. 아래에 표시된 파일의 오류를 해결한 뒤 다시 시도해 주세요." }, "resolveTheErrorsBelowAndTryAgain": { "message": "Resolve the errors below and try again." @@ -2969,7 +3126,7 @@ "message": "Description" }, "importSuccess": { - "message": "Data successfully imported" + "message": "데이터 가져오기 성공" }, "importSuccessNumberOfItems": { "message": "A total of $AMOUNT$ items were imported.", @@ -3005,7 +3162,7 @@ "message": "Launch Duo in Browser" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "데이터의 포맷이 올바르지 않습니다. 불러올 파일을 확인하고 다시 시도해 주십시오." }, "importNothingError": { "message": "Nothing was imported." @@ -3023,13 +3180,13 @@ "message": "Learn about your import options" }, "selectImportFolder": { - "message": "Select a folder" + "message": "폴더 선택" }, "selectImportCollection": { "message": "Select a collection" }, "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": { @@ -3070,7 +3227,7 @@ "message": "Confirm vault import" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "이 파일은 암호로 보호받고 있습니다. 데이터를 가져오려면 파일 암호를 입력하세요." }, "confirmFilePassword": { "message": "Confirm file password" @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index e065b4ebfb8..11c70dd4197 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Saugus įrašas" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Aplankai" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresas" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Reikalinga Premium narystė" }, @@ -189,6 +249,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Sausis" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopijuoti slaptažodį" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Serverio nuoroda" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Slaptažodžių istorija" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Išvalyti", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nėra rodytinų slaptažodžių." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Anuliuoti" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Patvirtinti Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Atrakinti naudojant Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Vartotojo vardo tipas" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Į jūsų įrenginį išsiųstas pranešimas." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Prašome užtikrinti jog jūsų saugykla atrakinta ir piršto antspaudo frazė sutampa su kitu įrenginiu." }, "fingerprintPhraseHeader": { "message": "Piršto antspaudo frazė" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Prisijungimas su įrenginiu turi būti nustatytas Bitwarden aplikacijos nustatymuose. Reikia kito pasirinkimo?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Peržiūrėti visas prisijungimo parinktis" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Silpnas slaptažodis nustatytas ir rastas per duomenų pažeidimą. Norėdami apsaugoti paskyrą, naudokite stiprų ir unikalų slaptažodį. Ar tikrai norite naudoti šį slaptažodį?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Patikrinti žinomus šio slaptažodžio duomenų pažeidimus" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Svarbu:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Rekomenduojamas nustatymų atnaujinimas" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Įrenginio patvirtinimas reikalingas. Pasirinkite patvirtinimo būdą toliau:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Prisiminti šį įrenginį" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Trūksta naudotojo el. pašto" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Patikimas įrenginys" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 264f65fc9b7..68495a25137 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Droša piezīme" }, + "typeSshKey": { + "message": "SSH atslēga" + }, "folders": { "message": "Mapes" }, @@ -177,6 +180,63 @@ "address": { "message": "Adrese" }, + "sshPrivateKey": { + "message": "Privātā atslēga" + }, + "sshPublicKey": { + "message": "Publiskā atslēga" + }, + "sshFingerprint": { + "message": "Pirkstu nospiedums" + }, + "sshKeyAlgorithm": { + "message": "Atslēgas veids" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Tika izveidota jauna SSH atslēga" + }, + "sshKeyWrongPassword": { + "message": "Ievadītā parole ir nepareiza." + }, + "importSshKey": { + "message": "Ievietot" + }, + "confirmSshKeyPassword": { + "message": "Apstiprināt paroli" + }, + "enterSshKeyPasswordDesc": { + "message": "Ievadīt SSH atslēgas paroli." + }, + "enterSshKeyPassword": { + "message": "Ievadīt paroli" + }, + "sshAgentUnlockRequired": { + "message": "Lūgums atslēgt savu glabātavu, lai apstiprinātu SSH atslēgas pieprasījumu." + }, + "sshAgentUnlockTimeout": { + "message": "SSH atslēgas pieprasījumam iestājās noildze." + }, + "enableSshAgent": { + "message": "Iespējot SSH aģentu" + }, + "enableSshAgentDesc": { + "message": "Iespējo SSH aģentu, lai parakstītu SSH pieprasījumus tieši no savas Bitwarden glabātavas." + }, + "enableSshAgentHelp": { + "message": "SSH aģents ir izstrādātājiem paredzēts pakalpojums, kas ļauj parakstīt SSH pieprasījumus tieši no savas Bitwarden glabātavas." + }, "premiumRequired": { "message": "Nepieciešams Premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvāris" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Ievietot paroli starpliktuvē" }, + "regenerateSshKey": { + "message": "Atkārtoti izveidot SSH atslēgu" + }, + "copySshPrivateKey": { + "message": "Ievietot privāto SSH atslēgu starpliktuvē" + }, "copyPassphrase": { "message": "Ievietot paroles vārdkopu starpliktuvē", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Servera URL" }, + "authenticationTimeout": { + "message": "Autentificēšanās noildze" + }, + "authenticationSessionTimedOut": { + "message": "Iestājās autentificēšanās sesijas noildze. Lūgums sākt pieteikšanos no jauna." + }, "selfHostBaseUrl": { "message": "Pašmitināta servera URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Paroles izmaiņu vēsture" }, + "generatorHistory": { + "message": "Veidotāja vēsture" + }, + "clearGeneratorHistoryTitle": { + "message": "Iztīrīt veidotāja vēsturi" + }, + "cleargGeneratorHistoryDescription": { + "message": "Turpinot visi veidotāja vēstures ieraksti tiks neatgrieziniski izdzēsti. Vai tiešām turpināt?" + }, "clear": { "message": "Notīrīt", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nav paroļu, ko parādīt." }, + "clearHistory": { + "message": "Iztīrīt vēsturi" + }, + "nothingToShow": { + "message": "Nav nekā, ko parādīt" + }, + "nothingGeneratedRecently": { + "message": "Pēdējā laikā nekas nav izveidots" + }, "undo": { "message": "Atsaukt" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Apstiprināt Bitwarden." }, - "polkitConsentMessage": { - "message": "Autentificēt, lai atslēgtu Bitwarden." - }, "unlockWithTouchId": { "message": "Atslēgt ar Touch ID" }, @@ -2391,10 +2492,10 @@ "message": "Izveidot lietotājvārdu" }, "generateEmail": { - "message": "Izveidot e-pastu" + "message": "Izveidot e-pasta adresi" }, - "generatorBoundariesHint": { - "message": "Vērtībai jābūt starp $MIN$ un $MAX$", + "spinboxBoundariesHint": { + "message": "Vērtībai jābūt starp $MIN$ un $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Jāizmanto $RECOMMENDED$ vai vairāk rakstzīmju, lai izveidotu spēcīgu paroli.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Jāizmanto $RECOMMENDED$ vai vairāk vārdu, lai izveidotu spēcīgu paroles vārdkopu.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Lietotājvārda veids" }, @@ -2448,7 +2569,7 @@ "message": "Pārvirzīto e-pastu aizstājvārds" }, "forwardedEmailDesc": { - "message": "Izveidot e-pastu aizstājvārdu ar ārēju pārvirzīšanas pakalpojumu." + "message": "Izveidot e-pasta aizstājadresi ar ārēju pārvirzīšanas pakalpojumu." }, "forwarderDomainName": { "message": "E-pasta domēns", @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Uz jūsu ierīci ir nosūtīts paziņojums." }, + "aNotificationWasSentToYourDevice": { + "message": "Uz ierīci tika nosūtīts paziņojums" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lūgums pārliecināties, ka konts ir atslēgts un atpazīšanas vārdkopa ir tāda pati arī otrā ierīcē" + }, + "needAnotherOptionV1": { + "message": "Nepieciešama cita iespēja?" + }, "fingerprintMatchInfo": { "message": "Lūgums pārliecināties, ka glabātava ir atslēgta un atpazīšanas vārdkopa ir tāda pati arī citā ierīcē." }, "fingerprintPhraseHeader": { "message": "Atpazīšanas vārdkopa" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Tiks paziņots, tiklīdz pieprasījums būs apstiprināts" + }, "needAnotherOption": { "message": "Ir jāuzstāda pieteikšanās ar ierīci Bitwarden lietotnes iestatījumos. Nepieciešama cita iespēja?" }, + "viewAllLogInOptions": { + "message": "Skatīt visas pieteikšanās iespējas" + }, "viewAllLoginOptions": { "message": "Skatīt visas pieteikšanās iespējas" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Noteikta vāja parole, un tā ir atrasta datu noplūdē. Jāizmanto spēcīga un neatkārtojama parole, lai aizsargātu savu kontu. Vai tiešām izmantot šo paroli?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Meklēt šo paroli zināmās datu noplūdēs" }, + "loggedInExclamation": { + "message": "Pieteicies." + }, "important": { "message": "Svarīgi:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Ieteicamie iestatījumu atjauninājumi" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Atcerēties šo ierīci, lai nākotnes pieteikšanos padarītu plūdenāku" + }, "deviceApprovalRequired": { "message": "Nepieciešams ierīces apstiprinājums. Zemāk jāatlasa apstiprinājuma iespēja:" }, + "deviceApprovalRequiredV2": { + "message": "Nepieciešama ierīces apstiprināšana" + }, + "selectAnApprovalOptionBelow": { + "message": "Zemāk jāatlasa apstiprināšnas iespēja" + }, "rememberThisDevice": { "message": "Atcerēties šo ierīci" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Trūkst lietotāja e-pasta adreses" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktīva lietotāja e-pasta adrese netika atrasta. Notiek atteikšanās." + }, "deviceTrusted": { "message": "Ierīce ir uzticama" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Netika atrasti brīvi vienotās (SSO) pieteikšanās porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, + "authorize": { + "message": "Pilnvarot" + }, + "deny": { + "message": "Noraidīt" + }, + "sshkeyApprovalTitle": { + "message": "Apstiprināt SSH atslēgas lietojumu" + }, + "sshkeyApprovalMessageInfix": { + "message": "pieprasa piekļuvi" + }, + "unknownApplication": { + "message": "Lietotne" + }, + "sshKeyPasswordUnsupported": { + "message": "Ar paroli aizsargātu SSH atslēgu ievietošana pagaidām netiek nodrošināta" + }, + "invalidSshKey": { + "message": "SSH atslēga ir nederīga" + }, + "sshKeyTypeUnsupported": { + "message": "SSH atslēgas veids netiek atbalstīts" + }, + "importSshKeyFromClipboard": { + "message": "Ievietot atslēgu no starpliktuves" + }, + "sshKeyPasted": { + "message": "SSH atslēga tika veiksmīgi ievietota" + }, "fileSavedToDevice": { "message": "Datne saglabāta ierīcē. Tā ir atrodama ierīces lejupielāžu mapē." + }, + "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 jaunās ierīcēs." + }, + "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" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 1562c6f1f4f..ac9994c3134 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sigurna belješka" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Fascikle" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresa" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium obavezan" }, @@ -189,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopiraj lozinku" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Istorija lozinki" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Očisti", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nema lozinki za prikazivanje." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Poništi" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verifikuj za Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Otključaj sa Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 9b947278f62..15d4de334eb 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "സുരക്ഷിത കുറിപ്പ്" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "ഫോൾഡറുകൾ" }, @@ -177,6 +180,63 @@ "address": { "message": "മേൽവിലാസം" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "പ്രീമിയം അംഗത്വം ആവശ്യമാണ്" }, @@ -189,6 +249,20 @@ "error": { "message": "പിശക്" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ജനുവരി" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "പാസ്‌വേഡ് പകർത്തുക" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "സെർവർ യു ർ ൽ" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "പാസ്സ്‌വേഡ് ചരിത്രം" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "മായ്ക്കുക", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "പ്രദർശിപ്പിക്കാൻ പാസ്സ്‌വേഡുകൾ ഒന്നും ഇല്ല." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "തിരിച്ചാക്കുക" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bitwarden വേണ്ടി പരിശോധിച്ചുറപ്പിക്കുക." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Touch ID ഉപയോഗിച്ച് അൺലോക്കുചെയ്യുക" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 811885b464d..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index e30721d1d0e..ca399731495 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "လုံခြုံတဲ့မှတ်စု" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 9459cd43eb5..0ebecf3be8f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Sikker notis" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Mapper" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresse" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium er påkrevd" }, @@ -189,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopier passordet" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Tjener-nettadresse" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Passordhistorikk" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Tøm", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Det er ingen passord å liste opp." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Angre" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bekreft for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Lås opp med Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Brukernavntype" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Et varsel har blitt sendt til enheten din." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingeravtrykksfrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Vis alle påloggingsalternativer" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Viktig:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Husk denne enheten" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 5182547dbc7..624547d2121 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "सुरक्षित नोट" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "फोल्डरहरू" }, @@ -177,6 +180,63 @@ "address": { "message": "ठेगाना" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "प्रिमियम आवश्यक छ" }, @@ -189,6 +249,20 @@ "error": { "message": "त्रुटि" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "जनवरी" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "पासवर्ड प्रतिलिपि गर्नुहोस्" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index bf1aadfeccb..6f3670afa3d 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Veilige notitie" }, + "typeSshKey": { + "message": "SSH-sleutel" + }, "folders": { "message": "Mappen" }, @@ -177,6 +180,63 @@ "address": { "message": "Adres" }, + "sshPrivateKey": { + "message": "Privésleutel" + }, + "sshPublicKey": { + "message": "Publieke sleutel" + }, + "sshFingerprint": { + "message": "Vingerafdruk" + }, + "sshKeyAlgorithm": { + "message": "Sleuteltype" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bit" + }, + "sshKeyGenerated": { + "message": "Nieuwe SSH-sleutel gegenereerd" + }, + "sshKeyWrongPassword": { + "message": "Het door jou ingevoerde wachtwoord is onjuist." + }, + "importSshKey": { + "message": "Importeer" + }, + "confirmSshKeyPassword": { + "message": "Wachtwoord bevestigen" + }, + "enterSshKeyPasswordDesc": { + "message": "Voer het wachtwoord voor de SSH sleutel in." + }, + "enterSshKeyPassword": { + "message": "Wachtwoord invoeren" + }, + "sshAgentUnlockRequired": { + "message": "Ontgrendel je kluis voor het goedkeuren van het SSH-sleutelverzoek." + }, + "sshAgentUnlockTimeout": { + "message": "Time-out SSH-sleutelaanvraag." + }, + "enableSshAgent": { + "message": "SSH-agent inschakelen" + }, + "enableSshAgentDesc": { + "message": "Schakel de SSH-agent in om SSH-verzoeken rechtstreeks vanuit je Bitwarden-kluis te ondertekenen." + }, + "enableSshAgentHelp": { + "message": "De SSH-agent is een dienst gericht op ontwikkelaars waarmee je SSH-verzoeken rechtstreeks vanuit je Bitwarden-kluis kunt ondertekenen." + }, "premiumRequired": { "message": "Premium vereist" }, @@ -189,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januari" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopieer wachtwoord" }, + "regenerateSshKey": { + "message": "SSH-sleutel opnieuw genereren" + }, + "copySshPrivateKey": { + "message": "Privésleutel kopiëren" + }, "copyPassphrase": { "message": "Wachtwoordzin kopiëren", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server-URL" }, + "authenticationTimeout": { + "message": "Authenticatie-timeout" + }, + "authenticationSessionTimedOut": { + "message": "De inlogsessie is verlopen. Start het inlogproces opnieuw op." + }, "selfHostBaseUrl": { "message": "URL zelfgehoste server", "description": "Label for field requesting a self-hosted integration service URL" @@ -873,7 +959,7 @@ "message": "Pictogrammenserver-URL" }, "environmentSaved": { - "message": "De omgevings-URL's zijn opgeslagen." + "message": "Omgevings-URL's opgeslagen" }, "ok": { "message": "Ok" @@ -930,7 +1016,7 @@ "message": "Nieuwe map toevoegen" }, "view": { - "message": "Beeld" + "message": "Weergeven" }, "account": { "message": "Account" @@ -1196,7 +1282,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "Controleren op updates" + "message": "Controleren op updates…" }, "version": { "message": "Versie $VERSION_NUM$", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Geschiedenis" }, + "generatorHistory": { + "message": "Generatorgeschiedenis" + }, + "clearGeneratorHistoryTitle": { + "message": "Generatorgeschiedenis wissen" + }, + "cleargGeneratorHistoryDescription": { + "message": "Als je doorgaat, wis je definitief de geschiedenis van de generator. Weet je zeker dat je wilt doorgaan?" + }, "clear": { "message": "Wissen", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Er zijn geen wachtwoorden om weer te geven." }, + "clearHistory": { + "message": "Geschiedenis wissen" + }, + "nothingToShow": { + "message": "Niets weer te geven" + }, + "nothingGeneratedRecently": { + "message": "Je hebt de laatste tijd niets gegenereerd" + }, "undo": { "message": "Ongedaan maken" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verifiëren voor Bitwarden." }, - "polkitConsentMessage": { - "message": "Verifieer om Bitwarden te ontgrendelen." - }, "unlockWithTouchId": { "message": "Ontgrendelen met Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "E-mailadres genereren" }, - "generatorBoundariesHint": { - "message": "Waarde moet tussen $MIN$ en $MAX$ liggen", + "spinboxBoundariesHint": { + "message": "Waarde moet tussen $MIN$ en $MAX$ liggen.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Gebruik $RECOMMENDED$ tekens of meer om een sterk wachtwoord te genereren.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Gebruik $RECOMMENDED$ woorden of meer om een sterke wachtwoordzin te genereren.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Type gebruikersnaam" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Er is een melding naar je apparaat verzonden." }, + "aNotificationWasSentToYourDevice": { + "message": "Er is een melding naar je apparaat verzonden" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Zorg ervoor dat je account is ontgrendeld en dat de vingerafdrukzin overeenkomt met het andere apparaat" + }, + "needAnotherOptionV1": { + "message": "Nog een optie nodig?" + }, "fingerprintMatchInfo": { "message": "Controleer of je kluis is ontgrendeld en de vingerafdrukzin overeenkomt met het andere apparaat." }, "fingerprintPhraseHeader": { "message": "Vingerafdrukzin" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Je krijgt een melding zodra de aanvraag is goedgekeurd" + }, "needAnotherOption": { "message": "Je moet Inloggen met apparaat instellen in de instellingen van de Bitwarden-app. Behoefte aan een andere mogelijkheid?" }, + "viewAllLogInOptions": { + "message": "Alle inlogopties bekijken" + }, "viewAllLoginOptions": { "message": "Alle loginopties bekijken" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zwak wachtwoord geïdentificeerd en gevonden in een datalek. Gebruik een sterk en uniek wachtwoord om je account te beschermen. Weet je zeker dat je dit wachtwoord wilt gebruiken?" }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, "checkForBreaches": { "message": "Bekende datalekken voor dit wachtwoord controleren" }, + "loggedInExclamation": { + "message": "Ingelogd!" + }, "important": { "message": "Belangrijk:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Aanbevolen instellingen" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Onthoud dit apparaat om in het vervolg naadloos in te loggen" + }, "deviceApprovalRequired": { "message": "Apparaattoestemming vereist. Kies een goedkeuringsoptie hieronder:" }, + "deviceApprovalRequiredV2": { + "message": "Toestemming apparaat vereist" + }, + "selectAnApprovalOptionBelow": { + "message": "Kies hieronder een goedkeuringsoptie" + }, "rememberThisDevice": { "message": "Dit apparaat onthouden" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Gebruikerse-mailadres ontbreekt" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mailadres voor actieve gebruiker niet gevonden. Je wordt uitgelogd." + }, "deviceTrusted": { "message": "Vertrouwd apparaat" }, @@ -2889,7 +3046,7 @@ } }, "multipleInputEmails": { - "message": "Een of meer e-mailadressen zijn ongeldig" + "message": "Eén of meer e-mailadressen zijn ongeldig" }, "inputTrimValidator": { "message": "Invoer mag niet alleen witruimte bevatten.", @@ -2914,7 +3071,7 @@ "message": "-- Type om te filteren --" }, "multiSelectLoading": { - "message": "Opties ophalen..." + "message": "Opties ophalen…" }, "multiSelectNotFound": { "message": "Geen items gevonden" @@ -3106,7 +3263,7 @@ "message": "LastPass Email" }, "importingYourAccount": { - "message": "Account impoteren..." + "message": "Account impoteren…" }, "lastPassMFARequired": { "message": "LastPass multifactor-authenticatie vereist" @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Er zijn geen vrije poorten gevonden voor de sso-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, + "authorize": { + "message": "Autoriseren" + }, + "deny": { + "message": "Afwijzen" + }, + "sshkeyApprovalTitle": { + "message": "Gebruik SSH-sleutel bevestigen" + }, + "sshkeyApprovalMessageInfix": { + "message": "vraagt toegang tot" + }, + "unknownApplication": { + "message": "Een applicatie" + }, + "sshKeyPasswordUnsupported": { + "message": "Importeren van met wachtwoord beveiligde SSH-sleutels wordt nog niet ondersteund" + }, + "invalidSshKey": { + "message": "De SSH-sleutel is ongeldig" + }, + "sshKeyTypeUnsupported": { + "message": "Het type SSH-sleutel is niet ondersteund" + }, + "importSshKeyFromClipboard": { + "message": "Sleutel van klembord importeren" + }, + "sshKeyPasted": { + "message": "SSH-sleutel succesvol geïmporteerd" + }, "fileSavedToDevice": { "message": "Bestand op apparaat opgeslagen. Beheer vanaf de downloads op je apparaat." + }, + "importantNotice": { + "message": "Belangrijke melding" + }, + "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" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 4609c02f3b7..df2f70ae839 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Trygg notat" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Mapper" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresse" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium er påkravt" }, @@ -189,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopier passordet" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Nettadresse for tenar" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Passordhistorikk" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Tøm", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Det er ingen passord å vise." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Angre" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index b38af07eb28..ea55a1c6029 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "ଫୋଲ୍ଡର୍" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "ତ୍ରୁଟି" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ଜାନୁଆରୀ" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 415f1dc720b..d7b950100f5 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Bezpieczna notatka" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Foldery" }, @@ -177,6 +180,63 @@ "address": { "message": "Adres" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Konto Premium jest wymagane" }, @@ -189,6 +249,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Styczeń" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopiuj hasło" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Adres URL serwera" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Historia hasła" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Wyczyść", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Brak haseł." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Cofnij" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Zweryfikuj dla Bitwarden." }, - "polkitConsentMessage": { - "message": "Uwierzytelnij, aby odblokować Bitwarden." - }, "unlockWithTouchId": { "message": "Odblokuj za pomocą Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Rodzaj nazwy użytkownika" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Powiadomienie zostało wysłane na urządzenie." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Upewnij się, że sejf jest odblokowany, a unikalny identyfikator konta pasuje do innego urządzenia." }, "fingerprintPhraseHeader": { "message": "Unikalny identyfikator konta" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Logowanie za pomocą urządzenia musi być włączone w ustawieniach aplikacji Bitwarden. Potrzebujesz innej opcji?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Zobacz wszystkie sposoby logowania" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Słabe hasło ujawnione w wyniku naruszenia ochrony danych. Użyj silnego i unikalnego hasła, aby chronić swoje konto. Czy na pewno chcesz użyć tego hasła?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Sprawdź znane naruszenia ochrony danych tego hasła" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Ważne:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Aktualizacja ustawień zalecanych" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Wymagane zatwierdzenie urządzenia. Wybierz opcję zatwierdzenia poniżej:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Zapamiętaj to urządzenie" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Brak adresu e-mail użytkownika" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Zaufano urządzeniu" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Nie znaleziono wolnych portów dla logowania SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "Plik zapisany na urządzeniu. Zarządzaj plikiem na swoim urządzeniu." + }, + "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" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index e70618d983f..312fe4e8e89 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Nota Segura" }, + "typeSshKey": { + "message": "Chave SSH" + }, "folders": { "message": "Pastas" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Bem vindo de volta" }, "moveToOrgDesc": { "message": "Escolha uma organização para a qual deseja mover este item. Mudar para uma organização transfere a propriedade do item para essa organização. Você não será mais o proprietário direto deste item depois que ele for movido." @@ -177,6 +180,63 @@ "address": { "message": "Endereço" }, + "sshPrivateKey": { + "message": "Chave privada" + }, + "sshPublicKey": { + "message": "Chave pública" + }, + "sshFingerprint": { + "message": "Impressão digital" + }, + "sshKeyAlgorithm": { + "message": "Tipo de chave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Uma nova chave SSH foi gerada" + }, + "sshKeyWrongPassword": { + "message": "Sua senha está incorreta." + }, + "importSshKey": { + "message": "Importar" + }, + "confirmSshKeyPassword": { + "message": "Confirme a senha" + }, + "enterSshKeyPasswordDesc": { + "message": "Digite a senha para a chave SSH." + }, + "enterSshKeyPassword": { + "message": "Insira sua senha" + }, + "sshAgentUnlockRequired": { + "message": "Por favor, desbloqueie seu cofre para aprovar a solicitação de chave SSH." + }, + "sshAgentUnlockTimeout": { + "message": "Solicitação de chave SSH expirada." + }, + "enableSshAgent": { + "message": "Habilitar SSH agent" + }, + "enableSshAgentDesc": { + "message": "Permitir que o agente SSH assine solicitações SSH diretamente do seu cofre Bitwarden." + }, + "enableSshAgentHelp": { + "message": "O agente SSH é um serviço direcionado a desenvolvedores que permite que você assine solicitações SSH diretamente do seu cofre do Bitwarden." + }, "premiumRequired": { "message": "Requer Assinatura Premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -263,7 +337,7 @@ "message": "Gerar Senha" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Gerar frase secreta" }, "type": { "message": "Tipo" @@ -400,8 +474,14 @@ "copyPassword": { "message": "Copiar Senha" }, + "regenerateSshKey": { + "message": "Regerar chave SSH" + }, + "copySshPrivateKey": { + "message": "Copiar chave SSH privada" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copiar senha", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -432,11 +512,11 @@ "message": "Caracteres especiais (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Incluir", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Incluir caracteres maiúsculos", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -444,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Incluir caracteres minúsculos", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -452,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Incluir números", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -460,7 +540,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Incluir caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -495,11 +575,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Evitar caracteres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Os requisitos de política empresarial foram aplicados às suas opções de gerador.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -558,7 +638,7 @@ "message": "Criar Conta" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novo no Bitwarden?" }, "setAStrongPassword": { "message": "Defina uma senha forte" @@ -570,16 +650,16 @@ "message": "Iniciar sessão" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Inicie a sessão no Bitwarden" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Iniciar sessão com a chave de acesso" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Fazer login com dispositivo" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usar login único" }, "submit": { "message": "Enviar" @@ -628,7 +708,7 @@ "message": "Juntar-se à organização" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Entrar em $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -643,16 +723,16 @@ "message": "Configurações" }, "accountEmail": { - "message": "Account email" + "message": "Email da conta" }, "requestHint": { - "message": "Request hint" + "message": "Pedir dica" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Dica da senha mestra" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" }, "passwordHint": { "message": "Dica da Senha" @@ -698,10 +778,10 @@ "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Sua nova conta foi criada!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Você está conectado!" }, "masterPassSent": { "message": "Enviamos um e-mail com a dica da sua senha mestra." @@ -853,8 +933,14 @@ "baseUrl": { "message": "URL do Servidor" }, + "authenticationTimeout": { + "message": "Tempo de autenticação esgotado" + }, + "authenticationSessionTimedOut": { + "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de login." + }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL do servidor auto-host", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1001,16 +1087,16 @@ "message": "Seu cofre está trancado. Verifique sua identidade para continuar." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Sua conta está bloqueada" }, "or": { - "message": "or" + "message": "ou" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloquear com a biometria" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloquear com a senha mestra" }, "unlock": { "message": "Desbloquear" @@ -1041,7 +1127,7 @@ "message": "Tempo Limite do Cofre" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Tempo esgotado" }, "vaultTimeoutDesc": { "message": "Escolha quando o tempo limite do seu cofre irá se esgotar e execute a ação selecionada." @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Copiar e-mail" }, "copySecurityCode": { "message": "Copiar Código de Segurança", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Histórico de senhas" }, + "generatorHistory": { + "message": "Histórico do gerador" + }, + "clearGeneratorHistoryTitle": { + "message": "Limpar histórico do gerador" + }, + "cleargGeneratorHistoryDescription": { + "message": "Se continuar, todas as entradas serão permanentemente excluídas do histórico do gerador. Tem certeza que deseja continuar?" + }, "clear": { "message": "Limpar", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Não existem senhas para listar." }, + "clearHistory": { + "message": "Limpar histórico" + }, + "nothingToShow": { + "message": "Nada para mostrar" + }, + "nothingGeneratedRecently": { + "message": "Você não gerou uma senha recentemente" + }, "undo": { "message": "Desfazer" }, @@ -1402,7 +1506,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Copiado com sucesso" }, "errorRefreshingAccessToken": { "message": "Erro ao Atualizar Token" @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verifique para o Bitwarden." }, - "polkitConsentMessage": { - "message": "Autentice para desbloquear o Bitwarden." - }, "unlockWithTouchId": { "message": "Desbloquear com o Touch ID" }, @@ -1672,7 +1773,7 @@ "message": "Recomendado para segurança." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "Bloquear com senha mestra ao reiniciar" }, "deleteAccount": { "message": "Excluir conta" @@ -1684,10 +1785,10 @@ "message": "A exclusão de sua conta é permanente. Não poderá ser desfeito." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Não é possível excluir conta" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Esta ação não pode ser concluída porque sua conta pertence a uma organização. Entre em contato com o administrador da sua organização para obter mais detalhes." }, "accountDeleted": { "message": "Conta excluída" @@ -1800,7 +1901,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "fora do $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1888,7 +1989,7 @@ "message": "Ativar integração com o navegador" }, "enableBrowserIntegrationDesc1": { - "message": "Used to allow biometric unlock in browsers that are not Safari." + "message": "Usado para permitir desbloqueio biométrico em navegadores que não são Safari." }, "enableDuckDuckGoBrowserIntegration": { "message": "Permitir integração ao navegador DuckDuckGo" @@ -2219,7 +2320,7 @@ "message": "Minutos" }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -2369,7 +2470,7 @@ "message": "Bloqueado" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Seu cofre está bloqueado" }, "unlocked": { "message": "Desbloqueado" @@ -2391,10 +2492,10 @@ "message": "Gerar usuário" }, "generateEmail": { - "message": "Generate email" + "message": "Gerar e-mail" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Valor deve ser entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": "", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilize as palavras $RECOMMENDED$ ou mais para gerar uma frase secreta 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo de usuário" }, @@ -2451,11 +2572,11 @@ "message": "Gere um alias de e-mail com um serviço externo de encaminhamento." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domínio de e-mail", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Escolha um domínio que seja suportado pelo serviço selecionado", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Uma notificação foi enviada para seu dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Certifique-se que sua conta esteja desbloqueada e que a frase de identificação corresponda à do outro dispositivo" + }, + "needAnotherOptionV1": { + "message": "Precisa de outra opção?" + }, "fingerprintMatchInfo": { "message": "Por favor, certifique-se de que o seu cofre esteja desbloqueado e a frase de identificação corresponda ao outro dispositivo." }, "fingerprintPhraseHeader": { "message": "Frase de identificação" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Você será notificado assim que a requisição for aprovada" + }, "needAnotherOption": { "message": "Login com dispositivo deve ser habilitado nas configurações do aplicativo móvel do Bitwarden. Necessita de outra opção?" }, + "viewAllLogInOptions": { + "message": "Ver todas as opções de login" + }, "viewAllLoginOptions": { "message": "Ver todas as opções de login" }, @@ -2743,14 +2879,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Verificar vazamentos de dados conhecidos para esta senha" }, + "loggedInExclamation": { + "message": "Sessão Iniciada!" + }, "important": { "message": "Importante:" }, "accessing": { - "message": "Accessing" + "message": "Acessando" }, "accessTokenUnableToBeDecrypted": { "message": "Você foi desconectado porque seu token de acesso não pôde ser descriptografado. Por favor, faça o login novamente para resolver esse problema." @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Atualização de configurações recomendadas" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Lembrar deste dispositivo para permanecer conectado" + }, "deviceApprovalRequired": { "message": "Aprovação do dispositivo necessária. Selecione uma opção de aprovação abaixo:" }, + "deviceApprovalRequiredV2": { + "message": "É necessária a aprovação do dispositivo" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecione uma opção de aprovação abaixo" + }, "rememberThisDevice": { "message": "Lembrar deste dispositivo" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "E-mail do usuário ausente" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mail de usuário ativo não encontrado. Desconectando." + }, "deviceTrusted": { "message": "Dispositivo confiável" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Nenhuma porta livre foi encontrada para o cliente final." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Autorizar" + }, + "deny": { + "message": "Negar" + }, + "sshkeyApprovalTitle": { + "message": "Confirmar uso da chave SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "está solicitando acesso para" + }, + "unknownApplication": { + "message": "Uma aplicação" + }, + "sshKeyPasswordUnsupported": { + "message": "Importar chaves SSH protegidas por senha ainda não é suportado" + }, + "invalidSshKey": { + "message": "A chave SSH é inválida" + }, + "sshKeyTypeUnsupported": { + "message": "O tipo de chave SSH não é suportado" + }, + "importSshKeyFromClipboard": { + "message": "Importar chave da área de transferência" + }, + "sshKeyPasted": { + "message": "Chave SSH importada com sucesso" + }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "Arquivo salvo no dispositivo. Gerencie a partir das transferências do seu dispositivo." + }, + "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, eu tenho" + }, + "turnOnTwoStepLogin": { + "message": "Ativar login em duas etapas" + }, + "changeAcctEmail": { + "message": "Alterar e-mail" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 6c1d0d98eb1..d88862a7980 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "Chave SSH" + }, "folders": { "message": "Pastas" }, @@ -177,6 +180,63 @@ "address": { "message": "Endereço" }, + "sshPrivateKey": { + "message": "Chave privada" + }, + "sshPublicKey": { + "message": "Chave pública" + }, + "sshFingerprint": { + "message": "Impressão digital" + }, + "sshKeyAlgorithm": { + "message": "Tipo de chave" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Foi gerada uma nova chave SSH" + }, + "sshKeyWrongPassword": { + "message": "A palavra-passe que introduziu está incorreta." + }, + "importSshKey": { + "message": "Importar" + }, + "confirmSshKeyPassword": { + "message": "Confirmar palavra-passe" + }, + "enterSshKeyPasswordDesc": { + "message": "Introduza a palavra-passe para a chave SSH." + }, + "enterSshKeyPassword": { + "message": "Introduzir palavra-passe" + }, + "sshAgentUnlockRequired": { + "message": "Por favor, desbloqueie o seu cofre para aprovar o pedido de chave SSH." + }, + "sshAgentUnlockTimeout": { + "message": "O pedido de chave SSH expirou." + }, + "enableSshAgent": { + "message": "Ativar o agente SSH" + }, + "enableSshAgentDesc": { + "message": "Ative o agente SSH para assinar pedidos SSH diretamente do seu cofre Bitwarden." + }, + "enableSshAgentHelp": { + "message": "O agente SSH é um serviço direcionado a programadores que lhe permite assinar pedidos SSH diretamente do seu cofre Bitwarden." + }, "premiumRequired": { "message": "É necessária uma subscrição Premium" }, @@ -189,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copiar palavra-passe" }, + "regenerateSshKey": { + "message": "Regenerar chave SSH" + }, + "copySshPrivateKey": { + "message": "Copiar chave privada SSH" + }, "copyPassphrase": { "message": "Copiar frase de acesso", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL do servidor" }, + "authenticationTimeout": { + "message": "Tempo limite de autenticação" + }, + "authenticationSessionTimedOut": { + "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de início de sessão." + }, "selfHostBaseUrl": { "message": "URL do servidor auto-hospedado", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Histórico de palavras-passe" }, + "generatorHistory": { + "message": "Histórico do gerador" + }, + "clearGeneratorHistoryTitle": { + "message": "Limpar o histórico do gerador" + }, + "cleargGeneratorHistoryDescription": { + "message": "Se continuar, todas as entradas serão permanentemente eliminadas do histórico do gerador. Tem a certeza de que pretende continuar?" + }, "clear": { "message": "Limpar", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Não existem palavras-passe para listar." }, + "clearHistory": { + "message": "Limpar histórico" + }, + "nothingToShow": { + "message": "Nada a mostrar" + }, + "nothingGeneratedRecently": { + "message": "Não gerou nada recentemente" + }, "undo": { "message": "Anular" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verificar para o Bitwarden." }, - "polkitConsentMessage": { - "message": "Autenticar para desbloquear o Bitwarden." - }, "unlockWithTouchId": { "message": "Desbloquear com Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Gerar e-mail" }, - "generatorBoundariesHint": { - "message": "O valor deve estar entre $MIN$ e $MAX$", + "spinboxBoundariesHint": { + "message": "O valor deve estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Utilize $RECOMMENDED$ caracteres ou mais para gerar uma palavra-passe forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilize $RECOMMENDED$ palavras ou mais para gerar uma frase de acesso 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo de nome de utilizador" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Foi enviada uma notificação para o seu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Foi enviada uma notificação para o seu dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Certifique-se de que a sua conta está desbloqueada e que a frase de impressão digital corresponde à do outro dispositivo" + }, + "needAnotherOptionV1": { + "message": "Precisa de outra opção?" + }, "fingerprintMatchInfo": { "message": "Por favor, certifique-se de que o cofre está desbloqueado e que a frase de impressão digital corresponde à do outro dispositivo." }, "fingerprintPhraseHeader": { "message": "Frase de impressão digital" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Será notificado quando o pedido for aprovado" + }, "needAnotherOption": { "message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Precisa de outra opção?" }, + "viewAllLogInOptions": { + "message": "Ver todas as opções de início de sessão" + }, "viewAllLoginOptions": { "message": "Ver todas as opções de início de sessão" }, @@ -2743,14 +2879,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, "checkForBreaches": { "message": "Verificar violações de dados conhecidas para esta palavra-passe" }, + "loggedInExclamation": { + "message": "Sessão iniciada!" + }, "important": { "message": "Importante:" }, "accessing": { - "message": "A aceder" + "message": "A aceder a" }, "accessTokenUnableToBeDecrypted": { "message": "A sua sessão foi encerrada porque o seu token de acesso não pôde ser desencriptado. Por favor, inicie sessão novamente para resolver este problema." @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Atualização de definições recomendadas" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Memorizar este dispositivo para facilitar futuros inícios de sessão" + }, "deviceApprovalRequired": { "message": "É necessária a aprovação do dispositivo. Selecione uma opção de aprovação abaixo:" }, + "deviceApprovalRequiredV2": { + "message": "Aprovação do dispositivo necessária" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecione uma opção de aprovação abaixo" + }, "rememberThisDevice": { "message": "Lembrar este dispositivo" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "E-mail do utilizador em falta" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "O e-mail do utilizador ativo não foi encontrado. A terminar a sessão." + }, "deviceTrusted": { "message": "Dispositivo de confiança" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Não foi possível encontrar portas livres para o início de sessão sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, + "authorize": { + "message": "Autorizar" + }, + "deny": { + "message": "Recusar" + }, + "sshkeyApprovalTitle": { + "message": "Confirmar a utilização da chave SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "está a pedir acesso a" + }, + "unknownApplication": { + "message": "Uma aplicação" + }, + "sshKeyPasswordUnsupported": { + "message": "A importação de chaves SSH protegidas por palavra-passe ainda não é suportada" + }, + "invalidSshKey": { + "message": "A chave SSH é inválida" + }, + "sshKeyTypeUnsupported": { + "message": "O tipo de chave SSH não é suportado" + }, + "importSshKeyFromClipboard": { + "message": "Importar chave da área de transferência" + }, + "sshKeyPasted": { + "message": "Chave SSH importada com sucesso" + }, "fileSavedToDevice": { "message": "Ficheiro guardado no dispositivo. Gira-o a partir das transferências do seu dispositivo." + }, + "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" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index b55cb01662c..d4ca5e21a5e 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Notă securizată" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Dosare" }, @@ -177,6 +180,63 @@ "address": { "message": "Adresă" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium necesar" }, @@ -189,6 +249,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ianuarie" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copiere parolă" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL server" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Istoric parole" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Ștergere", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Nicio parolă de afișat." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Anulare" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verificați pentru Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Deblocare cu Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tip de nume de utilizator" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index feffc818e4b..46e04b77704 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Защищенная заметка" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "folders": { "message": "Папки" }, @@ -177,6 +180,63 @@ "address": { "message": "Адрес" }, + "sshPrivateKey": { + "message": "Приватный ключ" + }, + "sshPublicKey": { + "message": "Публичный ключ" + }, + "sshFingerprint": { + "message": "Отпечаток" + }, + "sshKeyAlgorithm": { + "message": "Тип ключа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Был создан новый ключ SSH" + }, + "sshKeyWrongPassword": { + "message": "Введенный пароль неверен." + }, + "importSshKey": { + "message": "Импорт" + }, + "confirmSshKeyPassword": { + "message": "Подтвердите пароль" + }, + "enterSshKeyPasswordDesc": { + "message": "Введите пароль для ключа SSH." + }, + "enterSshKeyPassword": { + "message": "Введите пароль" + }, + "sshAgentUnlockRequired": { + "message": "Пожалуйста, разблокируйте свое хранилище для подтверждения запроса ключа SSH." + }, + "sshAgentUnlockTimeout": { + "message": "Время ожидания запроса ключа SSH истекло." + }, + "enableSshAgent": { + "message": "Включить агент SSH" + }, + "enableSshAgentDesc": { + "message": "Включите агент SSH, чтобы подписывать запросы SSH прямо из вашего хранилища Bitwarden." + }, + "enableSshAgentHelp": { + "message": "Агент SSH - это сервис, ориентированный на разработчиков, позволяющий подписывать запросы SSH непосредственно из вашего хранилища Bitwarden." + }, "premiumRequired": { "message": "Требуется Премиум" }, @@ -189,6 +249,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Январь" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Скопировать пароль" }, + "regenerateSshKey": { + "message": "Пересоздать ключ SSH" + }, + "copySshPrivateKey": { + "message": "Скопировать приватный ключ SSH" + }, "copyPassphrase": { "message": "Скопировать парольную фразу", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL сервера" }, + "authenticationTimeout": { + "message": "Тайм-аут аутентификации" + }, + "authenticationSessionTimedOut": { + "message": "Сеанс аутентификации завершился по времени. Пожалуйста, перезапустите процесс авторизации." + }, "selfHostBaseUrl": { "message": "URL собственного сервера", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "История паролей" }, + "generatorHistory": { + "message": "История генератора" + }, + "clearGeneratorHistoryTitle": { + "message": "Очистить историю генератора" + }, + "cleargGeneratorHistoryDescription": { + "message": "Если вы продолжите, все записи будут навсегда удалены из истории генератора. Вы уверены, что хотите продолжить?" + }, "clear": { "message": "Очистить", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Нет паролей для отображения." }, + "clearHistory": { + "message": "Очистить историю" + }, + "nothingToShow": { + "message": "Нечего показать" + }, + "nothingGeneratedRecently": { + "message": "Вы ничего не создавали в последнее время" + }, "undo": { "message": "Отменить" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Верификация для Bitwarden." }, - "polkitConsentMessage": { - "message": "Для разблокировки Bitwarden пройдите аутентификацию." - }, "unlockWithTouchId": { "message": "Разблокировать с Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Сгенерировать email" }, - "generatorBoundariesHint": { - "message": "Значение должно быть между $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Значение должно быть между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип имени пользователя" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "На ваше устройство отправлено уведомление." }, + "aNotificationWasSentToYourDevice": { + "message": "На ваше устройство было отправлено уведомление" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Убедитесь, что ваш аккаунт разблокирован и фраза отпечатка совпадает с фразой на другом устройстве" + }, + "needAnotherOptionV1": { + "message": "Нужен другой вариант?" + }, "fingerprintMatchInfo": { "message": "Убедитесь, что ваше хранилище разблокировано и фраза отпечатка совпадает на другом устройстве." }, "fingerprintPhraseHeader": { "message": "Фраза отпечатка" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Вы получите уведомление, когда запрос будет одобрен" + }, "needAnotherOption": { "message": "Вход с устройства должен быть настроен в настройках мобильного приложения Bitwarden. Нужен другой вариант?" }, + "viewAllLogInOptions": { + "message": "Посмотреть все варианты авторизации" + }, "viewAllLoginOptions": { "message": "Посмотреть все варианты авторизации" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Обнаружен слабый пароль, найденный в утечке данных. Используйте надежный и уникальный пароль для защиты вашего аккаунта. Вы уверены, что хотите использовать этот пароль?" }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, "checkForBreaches": { "message": "Проверять известные случаи утечки данных для этого пароля" }, + "loggedInExclamation": { + "message": "Выполнен вход!" + }, "important": { "message": "Важно:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Рекомендуемое обновление настроек" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запомнить это устройство, чтобы в будущем авторизовываться быстрее" + }, "deviceApprovalRequired": { "message": "Требуется одобрение устройства. Выберите вариант ниже:" }, + "deviceApprovalRequiredV2": { + "message": "Требуется подтверждение устройства" + }, + "selectAnApprovalOptionBelow": { + "message": "Выберите вариант подтверждения ниже" + }, "rememberThisDevice": { "message": "Запомнить это устройство" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Отсутствует email пользователя" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Email активного пользователя не найден. Разлогиниваем." + }, "deviceTrusted": { "message": "Доверенное устройство" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Не удалось найти свободные порты для авторизации SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, + "authorize": { + "message": "Разрешить" + }, + "deny": { + "message": "Запретить" + }, + "sshkeyApprovalTitle": { + "message": "Подтвердить использование ключа SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "запрашивает доступ к" + }, + "unknownApplication": { + "message": "Приложение" + }, + "sshKeyPasswordUnsupported": { + "message": "Импорт защищенных паролем ключей SSH пока не поддерживается" + }, + "invalidSshKey": { + "message": "Ключ SSH недействителен" + }, + "sshKeyTypeUnsupported": { + "message": "Тип ключа SSH не поддерживается" + }, + "importSshKeyFromClipboard": { + "message": "Импорт ключа из буфера обмена" + }, + "sshKeyPasted": { + "message": "Ключ SSH успешно импортирован" + }, "fileSavedToDevice": { "message": "Файл сохранен на устройстве. Управляйте им из загрузок устройства." + }, + "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 аккаунта" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index a9742d8b5ea..5372bf59637 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "ආරක්ෂිත සටහන" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "බහාලුම්" }, @@ -177,6 +180,63 @@ "address": { "message": "ලිපිනය" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "දෝෂය" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "දුරුතු" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "මුරපදය පිටපත් කරන්න" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 03f4a863fb7..07851ddc47e 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Zabezpečená poznámka" }, + "typeSshKey": { + "message": "Kľúč SSH" + }, "folders": { "message": "Priečinky" }, @@ -45,7 +48,7 @@ "message": "Zdieľať" }, "moveToOrganization": { - "message": "Presunúť do Organizácie" + "message": "Presunúť do organizácie" }, "movedItemToOrg": { "message": "$ITEMNAME$ presunuté do $ORGNAME$", @@ -177,6 +180,63 @@ "address": { "message": "Adresa" }, + "sshPrivateKey": { + "message": "Súkromný kľúč" + }, + "sshPublicKey": { + "message": "Verejný kľúč" + }, + "sshFingerprint": { + "message": "Odtlačok" + }, + "sshKeyAlgorithm": { + "message": "Typ kľúča" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Bol vygenerovaný nový kľúč SSH" + }, + "sshKeyWrongPassword": { + "message": "Zadané heslo je nesprávne." + }, + "importSshKey": { + "message": "Importovať" + }, + "confirmSshKeyPassword": { + "message": "Potvrdiť heslo" + }, + "enterSshKeyPasswordDesc": { + "message": "Zadajte heslo pre kľúč SSH." + }, + "enterSshKeyPassword": { + "message": "Zadať heslo" + }, + "sshAgentUnlockRequired": { + "message": "Prosím, odomknite svoj trezor, aby ste mohli schváliť žiadosť o kľúč SSH." + }, + "sshAgentUnlockTimeout": { + "message": "Požiadavka na kľúč SSH vypršala." + }, + "enableSshAgent": { + "message": "Povoliť agenta SSH" + }, + "enableSshAgentDesc": { + "message": "Povolí agentovi SSH podpisovať požiadavky SSH priamo z vášho trezoru Bitwarden." + }, + "enableSshAgentHelp": { + "message": "Agent SSH je služba určená pre vývojárov, ktorá vám umožňuje podpisovať požiadavky SSH priamo z vášho trezora Bitwarden." + }, "premiumRequired": { "message": "Vyžaduje prémiový účet" }, @@ -189,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Január" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Skopírovať heslo" }, + "regenerateSshKey": { + "message": "Generovať nový kľúč SSH" + }, + "copySshPrivateKey": { + "message": "Kopírovať súkromný kľúč SSH" + }, "copyPassphrase": { "message": "Kopírovať prístupovú frázu", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL servera" }, + "authenticationTimeout": { + "message": "Časový limit overenia" + }, + "authenticationSessionTimedOut": { + "message": "Relácia overovania skončila. Znovu spustite proces prihlásenia." + }, "selfHostBaseUrl": { "message": "Adresa URL vlastného hostingu", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "História hesla" }, + "generatorHistory": { + "message": "História generátora" + }, + "clearGeneratorHistoryTitle": { + "message": "Vymazať históriu generátora" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ak budete pokračovať, všetky položky z histórie generátora budu natrvalo vymazané. Naozaj chcete pokračovať?" + }, "clear": { "message": "Vyčistiť", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Neboli nájdené žiadne heslá." }, + "clearHistory": { + "message": "Vymazať históriu" + }, + "nothingToShow": { + "message": "Nie je čo zobraziť" + }, + "nothingGeneratedRecently": { + "message": "V poslednej dobe ste nič negenerovali" + }, "undo": { "message": "Späť" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Overiť sa pre Bitwarden." }, - "polkitConsentMessage": { - "message": "Overením odomknete Bitwarden." - }, "unlockWithTouchId": { "message": "Odomknúť s Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generovať e-mail" }, - "generatorBoundariesHint": { - "message": "Hodnota musí byť medzi $MIN$ a $MAX$", + "spinboxBoundariesHint": { + "message": "Hodnota musí byť medzi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Na vytvorenie silného hesla použite $RECOMMENDED$ znakov alebo viac.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Na vytvorenie silnej prístupovej frázy použite $RECOMMENDED$ slov alebo viac.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Typ používateľského mena" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie." }, + "aNotificationWasSentToYourDevice": { + "message": "Do vášho zariadenia bolo odoslané upozornenie" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Uistite sa, že je váš účet odomknutý a fráza odtlačku prsta sa zhoduje s frázou na druhom zariadení" + }, + "needAnotherOptionV1": { + "message": "Potrebujete inú možnosť?" + }, "fingerprintMatchInfo": { "message": "Skontrolujte, či je trezor odomknutý a či sa fráza odtlačku prsta zhoduje s druhým zariadením." }, "fingerprintPhraseHeader": { "message": "Fráza odtlačku prsta" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Po schválení žiadosti budete informovaní" + }, "needAnotherOption": { "message": "Prihlásenie pomocou zariadenia musí byť nastavené v nastaveniach aplikácie Bitwarden. Potrebujete inú možnosť?" }, + "viewAllLogInOptions": { + "message": "Zobraziť všetky možnosti prihlásenia" + }, "viewAllLoginOptions": { "message": "Zobraziť všetky možnosti prihlásenia" }, @@ -2743,14 +2879,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Nájdené slabé heslo v uniknuných údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" }, + "useThisPassword": { + "message": "Použiť toto heslo" + }, + "useThisUsername": { + "message": "Použiť toto používateľské meno" + }, "checkForBreaches": { "message": "Skontrolovať známe úniky údajov pre toto heslo" }, + "loggedInExclamation": { + "message": "Prihlásený!" + }, "important": { "message": "Dôležité:" }, "accessing": { - "message": "Accessing" + "message": "Pristupovanie" }, "accessTokenUnableToBeDecrypted": { "message": "Boli ste odhlásení, pretože váš prístupový token nebolo možné dešifrovať. Na vyriešenie tohto problému sa znova prihláste." @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Odporúčaná aktualizácia nastavenia" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamätať si toto zariadenie, pre budúce bezproblémové prihlásenie" + }, "deviceApprovalRequired": { "message": "Vyžaduje sa schválenie zariadenia. Vyberte možnosť schválenia nižšie:" }, + "deviceApprovalRequiredV2": { + "message": "Vyžaduje sa schválenie zariadenia" + }, + "selectAnApprovalOptionBelow": { + "message": "Vyberte možnosť schválenia nižšie" + }, "rememberThisDevice": { "message": "Zapamätať si toto zariadenie" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Chýba e-mail používateľa" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mail aktívneho používateľa sa nenašiel. Odhlasuje sa." + }, "deviceTrusted": { "message": "Dôveryhodné zariadenie" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Pre prihlásenie SSO sa nepodarilo nájsť žiadne voľné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je z neznámych dôvodov nedostupné." + }, + "authorize": { + "message": "Autorizovať" + }, + "deny": { + "message": "Odmietnuť" + }, + "sshkeyApprovalTitle": { + "message": "Potvrdiť použitie kľúča SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "žiada o prístup k" + }, + "unknownApplication": { + "message": "Aplikácia" + }, + "sshKeyPasswordUnsupported": { + "message": "Importovanie kľúčov SSH chránených heslom zatiaľ nie je podporované" + }, + "invalidSshKey": { + "message": "Kľúč SSH je neplatný" + }, + "sshKeyTypeUnsupported": { + "message": "Tento typ kľúča SSH nie je podporovaný" + }, + "importSshKeyFromClipboard": { + "message": "Importovať kľúč zo schránky" + }, + "sshKeyPasted": { + "message": "Kľúč SSH bol úspešne importovaný" + }, "fileSavedToDevice": { "message": "Súbor sa uložil do zariadenia. Spravujte stiahnuté súbory zo zariadenia." + }, + "importantNotice": { + "message": "Dôležité upozornenie" + }, + "setupTwoStepLogin": { + "message": "Nastaviť dvojstupňové prihlásenie" + }, + "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 spoľahlivý prístup k svojmu e-mailu, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nie, nemám" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Áno, mám spoľahlivý prístup k svojmu e-mailu" + }, + "turnOnTwoStepLogin": { + "message": "Zapnúť dvojstupňové prihlásenie" + }, + "changeAcctEmail": { + "message": "Zmeniť e-mail účtu" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 381e34658e9..c4fae11c815 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Varni zapisek" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Mape" }, @@ -177,6 +180,63 @@ "address": { "message": "Naslov" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Potrebno je premium članstvo" }, @@ -189,6 +249,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopiraj geslo" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL naslov strežnika" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Zgodovina gesla" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Počisti", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Ni gesel za na seznam." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Razveljavi" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Preverite za Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Odkleni z biometriko" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index fb1ad8e90f6..03cc5b8c265 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Сигурносна белешка" }, + "typeSshKey": { + "message": "SSH кључ" + }, "folders": { "message": "Фасцикле" }, @@ -177,6 +180,63 @@ "address": { "message": "Адреса" }, + "sshPrivateKey": { + "message": "Приватни кључ" + }, + "sshPublicKey": { + "message": "Јавни кључ" + }, + "sshFingerprint": { + "message": "Отисак" + }, + "sshKeyAlgorithm": { + "message": "Врста кључа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-бита" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-бита" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-бита" + }, + "sshKeyGenerated": { + "message": "Генерисан је нови SSH кључ" + }, + "sshKeyWrongPassword": { + "message": "Лозинка коју сте унели није тачна." + }, + "importSshKey": { + "message": "Увоз" + }, + "confirmSshKeyPassword": { + "message": "Потврда лозинке" + }, + "enterSshKeyPasswordDesc": { + "message": "Унети лозинку за SSH кључ." + }, + "enterSshKeyPassword": { + "message": "Унесите лозинку" + }, + "sshAgentUnlockRequired": { + "message": "Откључајте свој сеф да бисте одобрили захтев за SSH кључ." + }, + "sshAgentUnlockTimeout": { + "message": "Захтев за SSH кључем је истекао." + }, + "enableSshAgent": { + "message": "Упали SSH агент" + }, + "enableSshAgentDesc": { + "message": "Омогућите SSH агенту да потписује SSH захтеве директно из вашег Bitwarden сефа." + }, + "enableSshAgentHelp": { + "message": "SSH агент је услуга намењена програмерима која вам омогућава да потпишете SSH захтеве директно из вашег Bitwarden сефа." + }, "premiumRequired": { "message": "Потребан Премијум" }, @@ -189,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Јануар" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Копирај лозинку" }, + "regenerateSshKey": { + "message": "Регенеришите SSH кључ" + }, + "copySshPrivateKey": { + "message": "Копирајте SSH приватни кључ" + }, "copyPassphrase": { "message": "Копирај приступну фразу", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "УРЛ Сервера" }, + "authenticationTimeout": { + "message": "Истекло је време аутентификације" + }, + "authenticationSessionTimedOut": { + "message": "Истекло је време сесије за аутентификацију. Молим вас покрените процес пријаве поново." + }, "selfHostBaseUrl": { "message": "УРЛ сервера који се самостално хостује", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Историја Лозинке" }, + "generatorHistory": { + "message": "Генератор историје" + }, + "clearGeneratorHistoryTitle": { + "message": "Испразнити генератор историје" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ако наставите, сви уноси ће бити трајно избрисани из генератора историје. Да ли сте сигурни да желите да наставите?" + }, "clear": { "message": "Очисти", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Нема лозинки за приказивање." }, + "clearHistory": { + "message": "Обриши историју" + }, + "nothingToShow": { + "message": "Ништа за приказ" + }, + "nothingGeneratedRecently": { + "message": "Недавно нисте ништа генерисали" + }, "undo": { "message": "Опозови" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Потврди за Bitwarden." }, - "polkitConsentMessage": { - "message": "Аутентификујте се да бисте откључали Bitwarden." - }, "unlockWithTouchId": { "message": "Откључај са Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Генеришите имејл" }, - "generatorBoundariesHint": { - "message": "Вредност мора бити између $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Вредност мора бити између $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип имена" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." }, + "aNotificationWasSentToYourDevice": { + "message": "Обавештење је послато на ваш уређај" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" + }, + "needAnotherOptionV1": { + "message": "Треба Вам друга опција?" + }, "fingerprintMatchInfo": { "message": "Уверите се да је ваш сеф откључан и да се фраза отиска прста подудара на другом уређају." }, "fingerprintPhraseHeader": { "message": "Сигурносна фраза сефа" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Бићете обавештени када захтев буде одобрен" + }, "needAnotherOption": { "message": "Пријава помоћу уређаја мора бити подешена у подешавањима Bitwarden апликације. Потребна је друга опција?" }, + "viewAllLogInOptions": { + "message": "Погледајте сав извештај у опције" + }, "viewAllLoginOptions": { "message": "Погредајте све опције пријављивања" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Идентификована је слаба лозинка и пронађена у упаду података. Користите јаку и јединствену лозинку да заштитите свој налог. Да ли сте сигурни да желите да користите ову лозинку?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Проверите познате упада података за ову лозинку" }, + "loggedInExclamation": { + "message": "Пријављено!" + }, "important": { "message": "Важно:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Препоручено ажурирање поставки" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запамтити овај уређај да би будуће пријаве биле беспрекорне" + }, "deviceApprovalRequired": { "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, + "deviceApprovalRequiredV2": { + "message": "Потребно је одобрење уређаја" + }, + "selectAnApprovalOptionBelow": { + "message": "Изаберите опцију одобрења у наставку" + }, "rememberThisDevice": { "message": "Запамти овај уређај" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Недостаје имејл корисника" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Имејл активног корисника није пронађен. Одјављивање." + }, "deviceTrusted": { "message": "Уређај поуздан" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Нису пронађени портови за SSO пријаву." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Ауторизуј" + }, + "deny": { + "message": "Одбиј" + }, + "sshkeyApprovalTitle": { + "message": "Потврдите употребу SSH кључа" + }, + "sshkeyApprovalMessageInfix": { + "message": "тражи приступ" + }, + "unknownApplication": { + "message": "Апликација" + }, + "sshKeyPasswordUnsupported": { + "message": "Увоз лозинке заштићене SSH кључом још увек није подржано" + }, + "invalidSshKey": { + "message": "SSH кључ је неважећи" + }, + "sshKeyTypeUnsupported": { + "message": "Тип SSH кључа није подржан" + }, + "importSshKeyFromClipboard": { + "message": "Увезите кључ из оставе" + }, + "sshKeyPasted": { + "message": "SSH кључ је успешно увезен" + }, "fileSavedToDevice": { "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" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 3ba8e329043..920981908fa 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Säker anteckning" }, + "typeSshKey": { + "message": "SSH-nyckel" + }, "folders": { "message": "Mappar" }, @@ -98,7 +101,7 @@ "message": "Lösenord" }, "passphrase": { - "message": "Lösenordsfras" + "message": "Lösenfras" }, "editItem": { "message": "Redigera objekt" @@ -177,6 +180,63 @@ "address": { "message": "Adress" }, + "sshPrivateKey": { + "message": "Privat nyckel" + }, + "sshPublicKey": { + "message": "Offentlig nyckel" + }, + "sshFingerprint": { + "message": "Fingeravtryck" + }, + "sshKeyAlgorithm": { + "message": "Nyckeltyp" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "En ny SSH-nyckel har genererats" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Aktivera SSH-agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium krävs" }, @@ -189,6 +249,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Kopiera lösenord" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Kopiera privat SSH-nyckel" + }, "copyPassphrase": { "message": "Kopiera lösenfras", "description": "Copy passphrase to clipboard" @@ -432,11 +512,11 @@ "message": "Specialtecken (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Inkludera", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkludera versaler", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -444,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkludera gemener", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -452,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inkludera siffror", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -579,7 +659,7 @@ "message": "Logga in med enhet" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Använd Single Sign-On" }, "submit": { "message": "Skicka" @@ -815,7 +895,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Ange en kod som skickas per e-post." }, "loginUnavailable": { "message": "Inloggning ej tillgänglig" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server-URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -912,7 +998,7 @@ "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Har du redan skapat ett konto?" }, "logOutConfirmation": { "message": "Är du säker på att du vill logga ut?" @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Kopiera e-postadress" }, "copySecurityCode": { "message": "Kopiera säkerhetskod", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Lösenordshistorik" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Rensa", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Det finns inga lösenord att visa." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Ångra" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bekräfta för Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Lås upp med Touch ID" }, @@ -1684,7 +1785,7 @@ "message": "Att radera ditt konto är permanent. Det går inte att ångra." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Kan inte radera konto" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -1864,13 +1965,13 @@ "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, tillkännagivanden och forskningsmöjligheter från Bitwarden i din inkorg." }, "unsubscribe": { "message": "Unsubscribe" }, "atAnyTime": { - "message": "at any time." + "message": "när som helst." }, "byContinuingYouAgreeToThe": { "message": "By continuing, you agree to the" @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Typ av användarnamn" }, @@ -2451,7 +2572,7 @@ "message": "Skapa ett e-postalias med en extern vidarebefordranstjänst." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomän", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "En avisering har skickats till din enhet." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Se till att ditt valv är upplåst och att fingeravtrycksfrasen matchar på den andra enheten." }, "fingerprintPhraseHeader": { "message": "Fingeravtrycksfras" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "\"Logga in med enhet\" måste ställas in i inställningarna i Bitwardens app. Behöver du ett annat alternativ?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Visa alla inloggningsalternativ" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Lösenordet är svagt och avslöjades vid ett dataintrång. Använd ett starkt och unikt lösenord för att skydda ditt konto. Är det säkert att du vill använda detta lösenord?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontrollera kända dataintrång för detta lösenord" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Viktigt:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Rekommenderade inställningsuppdateringar" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Godkännande av enhet krävs. Välj ett godkännandealternativ nedan:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Kom ihåg denna enhet" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Användarens e-postadress saknas" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Enhet betrodd" }, @@ -3118,7 +3275,7 @@ "message": "Godkänn inloggningsbegäran i din autentiseringsapp eller ange en engångskod." }, "passcode": { - "message": "Kod" + "message": "Lösenkod" }, "lastPassMasterPassword": { "message": "LastPass Huvudlösenord" @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "begär tillgång till" + }, + "unknownApplication": { + "message": "En applikation" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "SSH-nyckeln är ogiltig" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Importera nyckel från urklipp" + }, + "sshKeyPasted": { + "message": "SSH-nyckel har importerats" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 811885b464d..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Folders" }, @@ -177,6 +180,63 @@ "address": { "message": "Address" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium required" }, @@ -189,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Copy password" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Server URL" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 2f02f780193..7f7e373132c 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "บันทึกการรักษาปลอดภัย" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "โฟลเดอร์" }, @@ -177,6 +180,63 @@ "address": { "message": "ที่อยู่" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium Required" }, @@ -189,6 +249,20 @@ "error": { "message": "ข้อผิดพลาด" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "มกราคม" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "คัดลอกรหัสผ่าน" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL ของเซิร์ฟเวอร์" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "ประวัติของรหัสผ่าน" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Clear", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Undo" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all login options" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Important:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 30626fe9dec..2fbd751ddb8 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Güvenli not" }, + "typeSshKey": { + "message": "SSH anahtarı" + }, "folders": { "message": "Klasörler" }, @@ -177,6 +180,63 @@ "address": { "message": "Adres" }, + "sshPrivateKey": { + "message": "Özel anahtar" + }, + "sshPublicKey": { + "message": "Ortak anahtar" + }, + "sshFingerprint": { + "message": "Parmak izi" + }, + "sshKeyAlgorithm": { + "message": "Anahtar türü" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "Girdiğiniz parola yanlış." + }, + "importSshKey": { + "message": "İçe aktar" + }, + "confirmSshKeyPassword": { + "message": "Parolayı onaylayın" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH anahtarının parolasını girin." + }, + "enterSshKeyPassword": { + "message": "Parolayı girin" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Premium gerekli" }, @@ -189,6 +249,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ocak" }, @@ -263,7 +337,7 @@ "message": "Parola oluştur" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Parola üret" }, "type": { "message": "Tür" @@ -400,8 +474,14 @@ "copyPassword": { "message": "Parolayı kopyala" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Parolayı kopyala", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -853,6 +933,12 @@ "baseUrl": { "message": "Sunucu URL'si" }, + "authenticationTimeout": { + "message": "Kimlik doğrulama zaman aşımı" + }, + "authenticationSessionTimedOut": { + "message": "Kimlik doğrulama oturumu zaman aşımına uğradı. Lütfen giriş sürecini yeniden başlatın." + }, "selfHostBaseUrl": { "message": "Kendi kendine barındırılan sunucu URL'si", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Parola geçmişi" }, + "generatorHistory": { + "message": "Üreteç geçmişi" + }, + "clearGeneratorHistoryTitle": { + "message": "Üreteç geçmişini temizle" + }, + "cleargGeneratorHistoryDescription": { + "message": "Devam ederseniz üreteç geçmişindeki tüm kayıtlar kalıcı olarak silinecektir. Devam etmek istediğinizden emin misiniz?" + }, "clear": { "message": "Temizle", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Listelenecek şifre yok." }, + "clearHistory": { + "message": "Geçmişi temizle" + }, + "nothingToShow": { + "message": "Gösterilecek bir şey yok" + }, + "nothingGeneratedRecently": { + "message": "Yakın zamanda herhangi bir şey üretmediniz" + }, "undo": { "message": "Geri al" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Bitwarden için doğrulayın." }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "Kilidi Touch ID ile aç" }, @@ -1684,10 +1785,10 @@ "message": "Hesabınızı silmek kalıcıdır. Geri alınamaz." }, "cannotDeleteAccount": { - "message": "Hesap silinemiyor" + "message": "Cannot delete account" }, "cannotDeleteAccountDesc": { - "message": "Hesabınızın sahibi bir kuruluş olduğu için bu işlem tamamlanamadı. Bilgi almak için kuruluş yöneticinizle iletişime geçin." + "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." }, "accountDeleted": { "message": "Hesap silindi" @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "E-posta oluştur" }, - "generatorBoundariesHint": { - "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır", + "spinboxBoundariesHint": { + "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Kullanıcı adı türü" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Cihazınıza bir bildirim gönderildi." }, + "aNotificationWasSentToYourDevice": { + "message": "Cihazınıza bir bildirim gönderildi" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lütfen hesabınızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun" + }, + "needAnotherOptionV1": { + "message": "Başka bir seçeneğe mi ihtiyacınız var?" + }, "fingerprintMatchInfo": { "message": "Lütfen kasanızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun." }, "fingerprintPhraseHeader": { "message": "Parmak izi ifadesi" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "İsteğiniz onaylanınca size haber vereceğiz" + }, "needAnotherOption": { "message": "Cihazla girişi Bitwarden mobil uygulamasının ayarlarından etkinleştirmelisiniz. Başka bir seçeneğe mi ihtiyacınız var?" }, + "viewAllLogInOptions": { + "message": "Tüm giriş seçeneklerini gör" + }, "viewAllLoginOptions": { "message": "Tüm giriş seçeneklerini gör" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Hem zayıf hem de veri ihlalinde yer alan bir tespit edildi. Hesabınızı korumak için güçlü bir parola seçin ve o parolayı başka yerlerde kullanmayın. Bu parolayı kullanmak istediğinizden emin misiniz?" }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, "checkForBreaches": { "message": "Bilinen veri ihlallerinde bu parolayı kontrol et" }, + "loggedInExclamation": { + "message": "Giriş yapıldı!" + }, "important": { "message": "Önemli:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Önerilen Ayarlar Güncellemesi" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Sonraki girişleri kolaylaştırmak için bu cihazı hatırla" + }, "deviceApprovalRequired": { "message": "Cihaz onayı gerekiyor. Lütfen onay yönteminizi seçin:" }, + "deviceApprovalRequiredV2": { + "message": "Cihazı onaylamanız gerekiyor" + }, + "selectAnApprovalOptionBelow": { + "message": "Aşağıdan bir onay yöntemi seçin" + }, "rememberThisDevice": { "message": "Bu cihazı hatırla" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Kullanıcının e-postası eksik" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktif kullanıcı e-postası bulunamadı. Çıkış yapılıyor." + }, "deviceTrusted": { "message": "Cihaza güvenildi" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "SSO girişi için açık port bulunamadı." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Yetkilendir" + }, + "deny": { + "message": "Reddet" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "Dosya cihaza kaydedildi. Cihazınızın indirilenler klasöründen yönetebilirsiniz." + }, + "importantNotice": { + "message": "Önemli uyarı" + }, + "setupTwoStepLogin": { + "message": "İki adımlı girişi ayarlayın" + }, + "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" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index a191b6526da..db0259adc05 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Захищена нотатка" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "folders": { "message": "Теки" }, @@ -177,6 +180,63 @@ "address": { "message": "Адреса" }, + "sshPrivateKey": { + "message": "Закритий ключ" + }, + "sshPublicKey": { + "message": "Відкритий ключ" + }, + "sshFingerprint": { + "message": "Цифровий відбиток" + }, + "sshKeyAlgorithm": { + "message": "Тип ключа" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "Згенеровано новий ключ SSH" + }, + "sshKeyWrongPassword": { + "message": "Ви ввели неправильний пароль." + }, + "importSshKey": { + "message": "Імпорт" + }, + "confirmSshKeyPassword": { + "message": "Підтвердити пароль" + }, + "enterSshKeyPasswordDesc": { + "message": "Введіть пароль для ключа SSH." + }, + "enterSshKeyPassword": { + "message": "Введіть пароль" + }, + "sshAgentUnlockRequired": { + "message": "Розблокуйте своє сховище, щоб затвердити запит ключа SSH." + }, + "sshAgentUnlockTimeout": { + "message": "Вичерпано час очікування запиту ключа SSH." + }, + "enableSshAgent": { + "message": "Увімкнути агента SSH" + }, + "enableSshAgentDesc": { + "message": "Увімкніть агента SSH, щоб підписувати запити SSH безпосередньо зі сховища Bitwarden." + }, + "enableSshAgentHelp": { + "message": "Агент SSH – це служба, призначена для розробників, яка дає можливість підписувати запити SSH безпосередньо зі сховища Bitwarden." + }, "premiumRequired": { "message": "Необхідна передплата преміум" }, @@ -189,6 +249,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Січень" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Копіювати пароль" }, + "regenerateSshKey": { + "message": "Повторно згенерувати ключ SSH" + }, + "copySshPrivateKey": { + "message": "Копіювати закритий ключ SSH" + }, "copyPassphrase": { "message": "Копіювати парольну фразу", "description": "Copy passphrase to clipboard" @@ -579,7 +659,7 @@ "message": "Увійти з пристроєм" }, "useSingleSignOn": { - "message": "Використовувати єдиний вхід" + "message": "Використати єдиний вхід" }, "submit": { "message": "Відправити" @@ -853,6 +933,12 @@ "baseUrl": { "message": "URL-адреса сервера" }, + "authenticationTimeout": { + "message": "Час очікування автентифікації" + }, + "authenticationSessionTimedOut": { + "message": "Час очікування сеансу автентифікації завершився. Перезапустіть процес входу в систему." + }, "selfHostBaseUrl": { "message": "URL-адреса власного сервера", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Історія паролів" }, + "generatorHistory": { + "message": "Історія генератора" + }, + "clearGeneratorHistoryTitle": { + "message": "Очистити історію генератора" + }, + "cleargGeneratorHistoryDescription": { + "message": "Якщо ви продовжите, усі записи будуть остаточно видалені з історії генератора. Справді продовжити?" + }, "clear": { "message": "Стерти", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Немає паролів." }, + "clearHistory": { + "message": "Очистити історію" + }, + "nothingToShow": { + "message": "Немає даних для показу" + }, + "nothingGeneratedRecently": { + "message": "Ви нічого не генерували останнім часом" + }, "undo": { "message": "Повернути" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Перевірити на Bitwarden." }, - "polkitConsentMessage": { - "message": "Автентифікуйтесь для розблокування Bitwarden." - }, "unlockWithTouchId": { "message": "Розблокувати з Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Генерувати е-пошту" }, - "generatorBoundariesHint": { - "message": "Значення має бути між $MIN$ та $MAX$", + "spinboxBoundariesHint": { + "message": "Значення має бути між $MIN$ та $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип імені користувача" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." }, + "aNotificationWasSentToYourDevice": { + "message": "Сповіщення надіслано на ваш пристрій" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Переконайтеся, що ваш обліковий запис розблоковано і фраза відбитка на іншому пристрої збігається" + }, + "needAnotherOptionV1": { + "message": "Потрібен інший варіант?" + }, "fingerprintMatchInfo": { "message": "Переконайтеся, що ваше сховище розблоковане, а фраза відбитка збігається з іншим пристроєм." }, "fingerprintPhraseHeader": { "message": "Фраза відбитка" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Після схвалення запиту ви отримаєте сповіщення" + }, "needAnotherOption": { "message": "Потрібно увімкнути схвалення запитів на вхід у налаштуваннях програми Bitwarden. Потрібен інший варіант?" }, + "viewAllLogInOptions": { + "message": "Переглянути всі варіанти входу" + }, "viewAllLoginOptions": { "message": "Переглянути всі варіанти входу" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Виявлено слабкий пароль, який знайдено у витоку даних. Використовуйте надійний та унікальний пароль для захисту свого облікового запису. Ви дійсно хочете використати цей пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Перевірити відомі витоки даних для цього пароля" }, + "loggedInExclamation": { + "message": "Ви увійшли!" + }, "important": { "message": "Важливо:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Оновлення рекомендованих налаштувань" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запам'ятайте цей пристрій, щоб спростити майбутні входи в систему" + }, "deviceApprovalRequired": { "message": "Необхідне підтвердження пристрою. Виберіть варіант підтвердження нижче:" }, + "deviceApprovalRequiredV2": { + "message": "Потрібне підтвердження пристрою" + }, + "selectAnApprovalOptionBelow": { + "message": "Виберіть варіант підтвердження нижче" + }, "rememberThisDevice": { "message": "Запам'ятати цей пристрій" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Немає адреси електронної пошти" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Адресу е-пошти активного користувача не знайдено. Виконується вихід із системи." + }, "deviceTrusted": { "message": "Довірений пристрій" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Не знайдено вільних портів для цього входу SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Авторизувати" + }, + "deny": { + "message": "Відмовити" + }, + "sshkeyApprovalTitle": { + "message": "Підтвердження використання ключа SSH" + }, + "sshkeyApprovalMessageInfix": { + "message": "запитує доступ до" + }, + "unknownApplication": { + "message": "Програма" + }, + "sshKeyPasswordUnsupported": { + "message": "Імпортування захищених паролем ключів SSH ще не підтримується" + }, + "invalidSshKey": { + "message": "Ключ SSH недійсний" + }, + "sshKeyTypeUnsupported": { + "message": "Тип ключа SSH не підтримується" + }, + "importSshKeyFromClipboard": { + "message": "Імпортувати ключ із буфера обміну" + }, + "sshKeyPasted": { + "message": "Ключ SSH успішно імпортовано" + }, "fileSavedToDevice": { "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": "Змінити адресу е-пошти" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 3f3c680b978..07dcdfd373c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "Ghi chú bảo mật" }, + "typeSshKey": { + "message": "SSH key" + }, "folders": { "message": "Thư mục" }, @@ -177,6 +180,63 @@ "address": { "message": "Địa chỉ" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, + "sshKeyAlgorithm": { + "message": "Key type" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "A new SSH key was generated" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "sshAgentUnlockRequired": { + "message": "Please unlock your vault to approve the SSH key request." + }, + "sshAgentUnlockTimeout": { + "message": "SSH key request timed out." + }, + "enableSshAgent": { + "message": "Enable SSH agent" + }, + "enableSshAgentDesc": { + "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + }, + "enableSshAgentHelp": { + "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + }, "premiumRequired": { "message": "Cần có tài khoản cao cấp" }, @@ -189,6 +249,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tháng 1" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "Sao chép Mật khẩu" }, + "regenerateSshKey": { + "message": "Regenerate SSH key" + }, + "copySshPrivateKey": { + "message": "Copy SSH private key" + }, "copyPassphrase": { "message": "Copy passphrase", "description": "Copy passphrase to clipboard" @@ -853,6 +933,12 @@ "baseUrl": { "message": "Địa chỉ máy chủ" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "Lịch sử mật khẩu" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "clear": { "message": "Xoá", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "Chưa có mật khẩu." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "undo": { "message": "Hoàn tác" }, @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "Xác minh cho Bitwarden." }, - "polkitConsentMessage": { - "message": "Xác thực để mở khóa Bitwarden." - }, "unlockWithTouchId": { "message": "Mở khóa với Touch ID" }, @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Loại tên người dùng" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "Vui lòng đảm bảo rằng bạn đã mở khoá kho và cụm vân tay khớp trên thiết bị khác." }, "fingerprintPhraseHeader": { "message": "Cụm vân tay" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "Đăng nhập bằng thiết bị phải được thiết lập trong cài đặt của ứng dụng Bitwarden. Dùng cách khác?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Xem tất cả tùy chọn đăng nhập" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mật khẩu yếu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kiểm tra mật khẩu có lộ trong các vụ rò rỉ dữ liệu hay không" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "Quan trọng:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "Cập nhật cài đặt được đề xuất" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Yêu cầu phê duyệt thiết bị. Chọn một tuỳ chọn phê duyệt bên dưới:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Lưu thiết bị này" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "Thiếu email người dùng" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Thiết bị tin cậy" }, @@ -3225,7 +3382,97 @@ "ssoError": { "message": "Không thể tìm thấy cổng trống để đăng nhập SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, + "authorize": { + "message": "Authorize" + }, + "deny": { + "message": "Deny" + }, + "sshkeyApprovalTitle": { + "message": "Confirm SSH key usage" + }, + "sshkeyApprovalMessageInfix": { + "message": "is requesting access to" + }, + "unknownApplication": { + "message": "An application" + }, + "sshKeyPasswordUnsupported": { + "message": "Importing password protected SSH keys is not yet supported" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyPasted": { + "message": "SSH key imported successfully" + }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." + }, + "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" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 1382becc6da..c9046b34a55 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "安全笔记" }, + "typeSshKey": { + "message": "SSH 密钥" + }, "folders": { "message": "文件夹" }, @@ -104,7 +107,7 @@ "message": "编辑项目" }, "emailAddress": { - "message": "电子邮件地址" + "message": "电子邮箱地址" }, "verificationCodeTotp": { "message": "验证码 (TOTP)" @@ -169,7 +172,7 @@ "message": "许可证号码" }, "email": { - "message": "电子邮件" + "message": "电子邮箱" }, "phone": { "message": "电话" @@ -177,6 +180,63 @@ "address": { "message": "地址" }, + "sshPrivateKey": { + "message": "私钥" + }, + "sshPublicKey": { + "message": "公钥" + }, + "sshFingerprint": { + "message": "指纹" + }, + "sshKeyAlgorithm": { + "message": "密钥类型" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "生成了新的 SSH 密钥" + }, + "sshKeyWrongPassword": { + "message": "您输入的密码不正确。" + }, + "importSshKey": { + "message": "导入" + }, + "confirmSshKeyPassword": { + "message": "确认密码" + }, + "enterSshKeyPasswordDesc": { + "message": "输入 SSH 密钥的密码。" + }, + "enterSshKeyPassword": { + "message": "输入密码" + }, + "sshAgentUnlockRequired": { + "message": "请解锁您的密码库以批准 SSH 密钥请求。" + }, + "sshAgentUnlockTimeout": { + "message": "SSH 密钥请求已超时。" + }, + "enableSshAgent": { + "message": "启用 SSH 代理" + }, + "enableSshAgentDesc": { + "message": "启用 SSH 代理以直接从您的 Bitwarden 密码库签署 SSH 请求。" + }, + "enableSshAgentHelp": { + "message": "SSH 代理是一个针对开发者的服务,允许您直接从 Bitwarden 密码库签署 SSH 请求。" + }, "premiumRequired": { "message": "需要高级版" }, @@ -189,6 +249,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -400,6 +474,12 @@ "copyPassword": { "message": "复制密码" }, + "regenerateSshKey": { + "message": "重新生成 SSH 密钥" + }, + "copySshPrivateKey": { + "message": "复制 SSH 私钥" + }, "copyPassphrase": { "message": "复制密码短语", "description": "Copy passphrase to clipboard" @@ -643,7 +723,7 @@ "message": "设置" }, "accountEmail": { - "message": "账户邮件地址" + "message": "账户电子邮箱" }, "requestHint": { "message": "请求提示" @@ -652,22 +732,22 @@ "message": "请求密码提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "输入您的账户电子邮件地址,您的密码提示将发送给您" + "message": "输入您的账户电子邮箱地址,您的密码提示将发送给您" }, "passwordHint": { "message": "密码提示" }, "enterEmailToGetHint": { - "message": "请输入您账户的电子邮件地址来接收主密码提示。" + "message": "请输入您的账户电子邮箱地址来接收主密码提示。" }, "getMasterPasswordHint": { "message": "获取主密码提示" }, "emailRequired": { - "message": "必须填写电子邮件地址。" + "message": "必须填写电子邮箱地址。" }, "invalidEmail": { - "message": "无效的电子邮件地址。" + "message": "无效的电子邮箱地址。" }, "masterPasswordRequired": { "message": "必须填写主密码。" @@ -676,7 +756,7 @@ "message": "必须填写确认主密码。" }, "masterPasswordMinlength": { - "message": "主密码必须至少 $VALUE$ 个字符长度。", + "message": "主密码长度必须至少为 $VALUE$ 个字符。", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -704,7 +784,7 @@ "message": "您已登录!" }, "masterPassSent": { - "message": "我们已经为您发送了包含主密码提示的邮件。" + "message": "我们已经为您发送了包含主密码提示的电子邮件。" }, "unexpectedError": { "message": "发生意外错误。" @@ -746,7 +826,7 @@ "message": "请输入您的验证器 App 中的 6 位数验证码。" }, "enterVerificationCodeEmail": { - "message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。", + "message": "请输入发送给 $EMAIL$ 的 6 位数验证码。", "placeholders": { "email": { "content": "$1", @@ -802,7 +882,7 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "为您的组织使用 Duo Security 的 Duo 移动应用、短信、电话或 U2F 安全钥匙来进行验证。", + "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全钥匙来进行验证。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { @@ -812,7 +892,7 @@ "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" }, "emailTitle": { - "message": "电子邮件" + "message": "电子邮箱" }, "emailDescV2": { "message": "输入发送到您的电子邮箱的代码。" @@ -853,6 +933,12 @@ "baseUrl": { "message": "服务器 URL" }, + "authenticationTimeout": { + "message": "身份验证超时" + }, + "authenticationSessionTimedOut": { + "message": "身份验证会话超时。请重新启动登录过程。" + }, "selfHostBaseUrl": { "message": "自托管服务器 URL", "description": "Label for field requesting a self-hosted integration service URL" @@ -903,13 +989,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -936,7 +1022,7 @@ "message": "账户" }, "loading": { - "message": "加载中…" + "message": "正在加载..." }, "lockVault": { "message": "锁定密码库" @@ -972,7 +1058,7 @@ "message": "前往网页 App 吗?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" + "message": "您可以在 Bitwarden 网页 App 上更改您的主密码。" }, "fingerprintPhrase": { "message": "指纹短语", @@ -986,7 +1072,7 @@ "message": "转到网页版密码库" }, "getMobileApp": { - "message": "获取手机应用" + "message": "获取移动 App" }, "getBrowserExtension": { "message": "获取浏览器扩展" @@ -1109,37 +1195,37 @@ "message": "显示网站图标" }, "faviconDesc": { - "message": "在每个登录项目旁边显示一个可识别的图像。" + "message": "在每个登录项目旁边显示可识别的图像。" }, "enableMinToTray": { "message": "最小化到托盘图标" }, "enableMinToTrayDesc": { - "message": "最小化窗口后,在系统托盘中显示一个图标。" + "message": "最小化窗口后,在系统托盘中显示图标。" }, "enableMinToMenuBar": { "message": "最小化到菜单栏" }, "enableMinToMenuBarDesc": { - "message": "最小化窗口后,改为在菜单栏中显示一个图标。" + "message": "最小化窗口后,改为在菜单栏中显示图标。" }, "enableCloseToTray": { "message": "关闭到托盘图标" }, "enableCloseToTrayDesc": { - "message": "关闭窗口后,在系统托盘中显示一个图标。" + "message": "关闭窗口后,在系统托盘中显示图标。" }, "enableCloseToMenuBar": { "message": "关闭到菜单栏" }, "enableCloseToMenuBarDesc": { - "message": "关闭窗口后,改为在菜单栏中显示一个图标。" + "message": "关闭窗口后,改为在菜单栏中显示图标。" }, "enableTray": { "message": "显示托盘图标" }, "enableTrayDesc": { - "message": "始终在系统托盘中显示一个图标。" + "message": "始终在系统托盘中显示图标。" }, "startToTray": { "message": "启动到托盘图标" @@ -1175,13 +1261,13 @@ "message": "语言" }, "languageDesc": { - "message": "更改应用程序所使用的语言。重新启动后生效。" + "message": "更改应用程序所使用的语言。重启后生效。" }, "theme": { "message": "主题" }, "themeDesc": { - "message": "更改本应用程序的颜色主题。" + "message": "更改应用程序的颜色主题。" }, "dark": { "message": "深色", @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "复制电子邮件地址" + "message": "复制电子邮箱" }, "copySecurityCode": { "message": "复制安全码", @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "密码历史记录" }, + "generatorHistory": { + "message": "生成器历史记录" + }, + "clearGeneratorHistoryTitle": { + "message": "清除生成器历史记录" + }, + "cleargGeneratorHistoryDescription": { + "message": "若继续,所有条目将从生成器历史记录中永久删除。确定要继续吗?" + }, "clear": { "message": "清除", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "没有可列出的密码。" }, + "clearHistory": { + "message": "清除历史记录" + }, + "nothingToShow": { + "message": "没有可显示的内容" + }, + "nothingGeneratedRecently": { + "message": "您最近没有生成任何内容" + }, "undo": { "message": "撤销" }, @@ -1545,7 +1649,7 @@ "description": "ex. Register as an accessibility user at hcaptcha.com" }, "copyPasteLink": { - "message": "复制链接并粘贴在下方以便发送到你的电子邮箱" + "message": "复制发送到您电子邮箱的链接然后粘贴在下方" }, "enterhCaptchaUrl": { "message": "输入 URL 以加载 hCaptcha 的无障碍 Cookie", @@ -1575,7 +1679,7 @@ "message": "确认密码库导出" }, "exportWarningDesc": { - "message": "导出的密码库数据包含未加密格式。您不应该通过不安全的渠道(例如电子邮件)来存储或发送导出的文件。用完后请立即将其删除。" + "message": "此导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。使用完后请立即将其删除。" }, "encExportKeyWarningDesc": { "message": "此导出将使用您账户的加密密钥来加密您的数据。如果您曾经轮换过账户的加密密钥,您应将其重新导出,否则您将无法解密导出的文件。" @@ -1621,7 +1725,7 @@ "message": "使用 PIN 码解锁" }, "setYourPinCode": { - "message": "设定您用来解锁 Bitwarden 的 PIN 码。您的 PIN 设置将在您完全注销本应用程序时被重置。" + "message": "设置用于解锁 Bitwarden 的 PIN 码。您的 PIN 设置将在您完全注销应用程序时被重置。" }, "pinRequired": { "message": "需要 PIN 码。" @@ -1644,9 +1748,6 @@ "windowsHelloConsentMessage": { "message": "验证 Bitwarden。" }, - "polkitConsentMessage": { - "message": "验证以解锁 Bitwarden。" - }, "unlockWithTouchId": { "message": "使用触控 ID 解锁" }, @@ -1666,7 +1767,7 @@ "message": "应用程序启动时要求使用触控 ID" }, "requirePasswordOnStart": { - "message": "应用程序启动时要求输入密码或 PIN 码" + "message": "App 启动时要求输入密码或 PIN 码" }, "recommendedForSecurity": { "message": "安全起见,推荐设置。" @@ -1727,7 +1828,7 @@ "message": "服务条款" }, "privacyPolicy": { - "message": "隐私条款" + "message": "隐私政策" }, "unsavedChangesConfirmation": { "message": "确定要离开吗?如果您现在离开,您当前的信息不会被保存。" @@ -1870,13 +1971,13 @@ "message": "取消订阅" }, "atAnyTime": { - "message": "随时" + "message": "随时。" }, "byContinuingYouAgreeToThe": { "message": "若继续,代表您同意" }, "and": { - "message": "以及" + "message": "和" }, "acceptPolicies": { "message": "选中此框表示您同意:" @@ -2138,19 +2239,19 @@ "message": "验证 WebAuthn" }, "hideEmail": { - "message": "对收件人隐藏我的电子邮件地址。" + "message": "对收件人隐藏我的电子邮箱地址。" }, "sendOptionsPolicyInEffect": { "message": "一个或多个组织策略正在影响您的 Send 设置。" }, "emailVerificationRequired": { - "message": "需要验证电子邮件" + "message": "需要验证电子邮箱" }, "emailVerifiedV2": { "message": "电子邮箱已验证" }, "emailVerificationRequiredDesc": { - "message": "您必须验证您的电子邮件才能使用此功能。" + "message": "您必须验证电子邮箱才能使用此功能。" }, "passwordPrompt": { "message": "主密码重新提示" @@ -2168,7 +2269,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2315,7 +2416,7 @@ "message": "偏好设置" }, "appPreferences": { - "message": "应用设置(所有账户)" + "message": "应用程序设置(所有账户)" }, "accountSwitcherLimitReached": { "message": "已达到账户上限。请注销一个账户后再添加其他账户。" @@ -2391,10 +2492,10 @@ "message": "生成用户名" }, "generateEmail": { - "message": "生成邮件地址" + "message": "生成电子邮箱" }, - "generatorBoundariesHint": { - "message": "值必须在 $MIN$ 和 $MAX$ 之间", + "spinboxBoundariesHint": { + "message": "值必须在 $MIN$ 和 $MAX$ 之间。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,18 +2508,38 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "用户名类型" }, "plusAddressedEmail": { - "message": "附加地址电子邮件", + "message": "附加地址电子邮箱", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "使用您的电子邮件供应商的子地址功能。" + "message": "使用您的电子邮箱提供商的子地址功能。" }, "catchallEmail": { - "message": "Catch-all 电子邮件" + "message": "Catch-all 电子邮箱" }, "catchallEmailDesc": { "message": "使用您的域名配置的 Catch-all 收件箱。" @@ -2445,13 +2566,13 @@ "message": "搜索我的密码库" }, "forwardedEmail": { - "message": "转发的电子邮件别名" + "message": "转发的电子邮箱别名" }, "forwardedEmailDesc": { - "message": "使用外部转发服务生成一个电子邮件别名。" + "message": "使用外部转发服务生成一个电子邮箱别名。" }, "forwarderDomainName": { - "message": "邮件域名", + "message": "电子邮箱域名", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2511,7 +2632,7 @@ } }, "forwarderNoAccountId": { - "message": "无法获取 $SERVICENAME$ 电子邮件账户 ID。", + "message": "无法获取 $SERVICENAME$ 电子邮箱账户 ID。", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2592,7 +2713,7 @@ "message": "正登录为" }, "rememberEmail": { - "message": "记住电子邮件地址" + "message": "记住电子邮箱" }, "notYou": { "message": "不是您吗?" @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "通知已发送到您的设备。" }, + "aNotificationWasSentToYourDevice": { + "message": "通知已发送到您的设备" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "确保您的账户已解锁,并且指纹短语与其他设备上的相匹配。" + }, + "needAnotherOptionV1": { + "message": "需要其他选项吗?" + }, "fingerprintMatchInfo": { "message": "请确保您的密码库已解锁,并且指纹短语与其他设备上的相匹配。" }, "fingerprintPhraseHeader": { "message": "指纹短语" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "请求获得批准后,您将收到通知" + }, "needAnotherOption": { "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他登录选项吗?" }, + "viewAllLogInOptions": { + "message": "查看所有登录选项" + }, "viewAllLoginOptions": { "message": "查看所有登录选项" }, @@ -2711,7 +2847,7 @@ "message": "已请求登录" }, "creatingAccountOn": { - "message": "正创建账户于" + "message": "创建账户至" }, "checkYourEmail": { "message": "检查您的电子邮箱" @@ -2729,7 +2865,7 @@ "message": "返回" }, "toEditYourEmailAddress": { - "message": "编辑您的电子邮件地址。" + "message": "编辑您的电子邮箱地址。" }, "exposedMasterPassword": { "message": "已暴露的主密码" @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护你的账户。确定要使用这个密码吗?" }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisUsername": { + "message": "使用此用户名" + }, "checkForBreaches": { "message": "检查已知的数据泄露是否包含此密码" }, + "loggedInExclamation": { + "message": "已登录!" + }, "important": { "message": "重要事项:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "推荐的设置更新" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "记住此设备以便将来无缝登录" + }, "deviceApprovalRequired": { "message": "需要设备批准。请在下面选择一个批准选项:" }, + "deviceApprovalRequiredV2": { + "message": "需要设备批准" + }, + "selectAnApprovalOptionBelow": { + "message": "在下方选择一个批准选项" + }, "rememberThisDevice": { "message": "记住此设备" }, @@ -2829,7 +2983,10 @@ "message": "登录已批准" }, "userEmailMissing": { - "message": "缺少用户电子邮件" + "message": "缺少用户电子邮箱" + }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "未找到活动的用户电子邮箱。您将被注销。" }, "deviceTrusted": { "message": "设备已信任" @@ -2889,14 +3046,14 @@ } }, "multipleInputEmails": { - "message": "一个或多个电子邮件地址无效" + "message": "一个或多个电子邮箱无效" }, "inputTrimValidator": { "message": "输入不能只包含空格。", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "输入的不是电子邮件地址。" + "message": "输入的不是电子邮箱地址。" }, "fieldsNeedAttention": { "message": "上面的 $COUNT$ 个字段需要您注意。", @@ -2993,7 +3150,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 寻求帮助。" + "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 获取协助。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 Duo 然后按照步骤完成登录。" @@ -3225,7 +3382,97 @@ "ssoError": { "message": "找不到用于 SSO 登录的可用端口。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, + "authorize": { + "message": "批准" + }, + "deny": { + "message": "拒绝" + }, + "sshkeyApprovalTitle": { + "message": "确认 SSH 密钥的使用" + }, + "sshkeyApprovalMessageInfix": { + "message": "正在请求访问" + }, + "unknownApplication": { + "message": "某个应用程序" + }, + "sshKeyPasswordUnsupported": { + "message": "尚不支持导入受密码保护的 SSH 密钥" + }, + "invalidSshKey": { + "message": "此 SSH 密钥无效" + }, + "sshKeyTypeUnsupported": { + "message": "不支持此 SSH 密钥类型" + }, + "importSshKeyFromClipboard": { + "message": "从剪贴板导入密钥" + }, + "sshKeyPasted": { + "message": "SSH 密钥导入成功" + }, "fileSavedToDevice": { "message": "文件已保存到设备。可以在设备下载中进行管理。" + }, + "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": "更改账户电子邮箱" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index c1c84ff912e..a9206a50df6 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -26,6 +26,9 @@ "typeSecureNote": { "message": "安全筆記" }, + "typeSshKey": { + "message": "SSH 金鑰" + }, "folders": { "message": "資料夾" }, @@ -61,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "moveToOrgDesc": { "message": "選擇您希望將這個項目移動到哪個組織。項目的擁有權將會轉移到該組織。一經移動,您將不再是此項目的直接擁有者。" @@ -177,6 +180,63 @@ "address": { "message": "地址" }, + "sshPrivateKey": { + "message": "私密金鑰" + }, + "sshPublicKey": { + "message": "公開金鑰" + }, + "sshFingerprint": { + "message": "指紋" + }, + "sshKeyAlgorithm": { + "message": "金鑰類型" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, + "sshKeyGenerated": { + "message": "一組SSH金鑰已在之前生成了" + }, + "sshKeyWrongPassword": { + "message": "您輸入的密碼錯誤。" + }, + "importSshKey": { + "message": "匯入" + }, + "confirmSshKeyPassword": { + "message": "確認密碼" + }, + "enterSshKeyPasswordDesc": { + "message": "輸入 SSH 金鑰的密碼" + }, + "enterSshKeyPassword": { + "message": "請輸入密碼" + }, + "sshAgentUnlockRequired": { + "message": "請解鎖密碼庫以核准SSh金鑰的請求" + }, + "sshAgentUnlockTimeout": { + "message": "SSH金鑰請求超時" + }, + "enableSshAgent": { + "message": "啟用 SSH 代理程式" + }, + "enableSshAgentDesc": { + "message": "啟用SSBitwardenH代理以從 Bitwarden 密碼庫簽發SSH請求" + }, + "enableSshAgentHelp": { + "message": "SSH代理是一個針對開發者的服務,它能夠直接從 Bitwarden 密碼庫簽發SSH請求。" + }, "premiumRequired": { "message": "需要進階會員資格" }, @@ -189,6 +249,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -263,7 +337,7 @@ "message": "產生密碼" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "產生密碼片語" }, "type": { "message": "類型" @@ -400,8 +474,14 @@ "copyPassword": { "message": "複製密碼" }, + "regenerateSshKey": { + "message": "重新產生密碼片語" + }, + "copySshPrivateKey": { + "message": "複製SSH私鑰" + }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "複製密碼片語", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -432,11 +512,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": { @@ -444,7 +524,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": { @@ -452,7 +532,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": { @@ -460,7 +540,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" }, "specialCharactersLabel": { @@ -495,11 +575,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "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." }, "searchCollection": { @@ -558,28 +638,28 @@ "message": "建立帳戶" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "第一次使用 Bitwarden?" }, "setAStrongPassword": { "message": "設定一個強密碼" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "設定密碼以完成帳號創建" }, "logIn": { "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "以通行密鑰 (passkey) 登入" }, "loginWithDevice": { - "message": "Log in with device" + "message": "使用裝置登入" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "使用單一登入" }, "submit": { "message": "送出" @@ -600,7 +680,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", @@ -628,7 +708,7 @@ "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -637,7 +717,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "設定主密碼以完成加入這個組織" }, "settings": { "message": "設定" @@ -646,13 +726,13 @@ "message": "電子郵件帳戶:" }, "requestHint": { - "message": "Request hint" + "message": "請求提示" }, "requestPasswordHint": { "message": "主密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "輸入您帳號的電子郵件,您的密碼提示會傳送給您" }, "passwordHint": { "message": "密碼提示" @@ -698,10 +778,10 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "你已經登入!" }, "masterPassSent": { "message": "已寄出包含您主密碼提示的電子郵件。" @@ -734,7 +814,7 @@ "message": "必須填入驗證碼。" }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "驗證已被取消或時間過長。請再試一次。" }, "invalidVerificationCode": { "message": "無效的驗證碼" @@ -788,17 +868,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 來存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -815,7 +895,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "loginUnavailable": { "message": "登入無法使用" @@ -836,13 +916,13 @@ "message": "指定您本地托管的 Bitwarden 安裝之基礎 URL。" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 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": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" @@ -853,8 +933,14 @@ "baseUrl": { "message": "伺服器 URL" }, + "authenticationTimeout": { + "message": "驗證逾時" + }, + "authenticationSessionTimedOut": { + "message": "驗證工作階段因時間過久已逾時。請重試登入。" + }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -897,22 +983,22 @@ "message": "已登出" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "您已經登出了您的帳號。" }, "loginExpired": { "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": "您確定要登出嗎?" @@ -969,10 +1055,10 @@ "message": "變更主密碼" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "前往網頁應用程式嗎?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式上變更主密碼。" }, "fingerprintPhrase": { "message": "指紋短語", @@ -1001,16 +1087,16 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "您的帳戶已被鎖定。" }, "or": { - "message": "or" + "message": "或" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "使用生物辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "unlock": { "message": "解鎖" @@ -1041,7 +1127,7 @@ "message": "密碼庫逾時時間" }, "vaultTimeout1": { - "message": "Timeout" + "message": "逾時" }, "vaultTimeoutDesc": { "message": "選擇密碼庫何時執行密碼庫逾時動作。" @@ -1248,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copySecurityCode": { "message": "複製安全代碼", @@ -1297,7 +1383,7 @@ "message": "您可以在 bitwarden.com 網頁版密碼庫購買進階會員資格。現在要前往嗎?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式的帳號設定中購買進階版。" }, "premiumCurrentMember": { "message": "您目前是進階會員!" @@ -1320,6 +1406,15 @@ "passwordHistory": { "message": "密碼歷史記錄" }, + "generatorHistory": { + "message": "產生器歷史記錄" + }, + "clearGeneratorHistoryTitle": { + "message": "清除產生器歷史記錄" + }, + "cleargGeneratorHistoryDescription": { + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" + }, "clear": { "message": "清除", "description": "To clear something out. example: To clear browser history." @@ -1327,6 +1422,15 @@ "noPasswordsInList": { "message": "沒有可列出的密碼。" }, + "clearHistory": { + "message": "清除歷史紀錄" + }, + "nothingToShow": { + "message": "沒有可顯示的內容" + }, + "nothingGeneratedRecently": { + "message": "您最近未產生任何密碼" + }, "undo": { "message": "復原" }, @@ -1402,13 +1506,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "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 密鑰。請重試登出再登入。" }, "help": { "message": "說明" @@ -1498,7 +1602,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "匯出自" }, "exportVault": { "message": "匯出密碼庫" @@ -1630,7 +1734,7 @@ "message": "無效的 PIN 碼。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "輸入太多無效 PIN 碼。 正在登出。" }, "unlockWithWindowsHello": { "message": "使用 Windows Hello 解鎖" @@ -1639,14 +1743,11 @@ "message": "額外的 Windows Hello 設定" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "使用系統驗證解鎖" }, "windowsHelloConsentMessage": { "message": "驗證 Bitwarden。" }, - "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." - }, "unlockWithTouchId": { "message": "使用 Touch ID 解鎖" }, @@ -1660,7 +1761,7 @@ "message": "啟動應用程式時詢問 Windows Hello" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "在啟動時詢問系統驗證" }, "autoPromptTouchId": { "message": "啟動應用程式時要求 Touch ID" @@ -1672,7 +1773,7 @@ "message": "為提升安全,建議使用。" }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "重啟後使用主密碼鎖定" }, "deleteAccount": { "message": "刪除帳戶" @@ -1684,7 +1785,7 @@ "message": "刪除您的帳戶是永久性的。並且無法復原。" }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "無法刪除帳號" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -2393,8 +2494,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2407,6 +2508,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "使用者名稱類型" }, @@ -2618,15 +2739,30 @@ "notificationSentDevice": { "message": "已傳送通知至您的裝置。" }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "fingerprintMatchInfo": { "message": "請確保您的密碼庫已解鎖,並且指紋短語與其他裝置的一致。" }, "fingerprintPhraseHeader": { "message": "指紋短語" }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "needAnotherOption": { "message": "必須先在 Bitwarden 應用程式設定中開啟,才可以使用裝置登入。要改用其他選項嗎?" }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "檢視所有登入選項" }, @@ -2743,9 +2879,18 @@ "weakAndBreachedMasterPasswordDesc": { "message": "密碼強度不足,且該密碼已洩露。請使用一個強度足夠和獨特的密碼來保護您的帳戶。您確定要使用這個密碼嗎?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "檢查外洩的密碼資料庫中是否包含此密碼" }, + "loggedInExclamation": { + "message": "Logged in!" + }, "important": { "message": "重要:" }, @@ -2776,9 +2921,18 @@ "windowsBiometricUpdateWarningTitle": { "message": "建議設定更新" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, + "deviceApprovalRequiredV2": { + "message": "需要核准裝置" + }, + "selectAnApprovalOptionBelow": { + "message": "選擇下面的一個核准選項" + }, "rememberThisDevice": { "message": "記住這個裝置" }, @@ -2831,6 +2985,9 @@ "userEmailMissing": { "message": "缺少使用者電子郵件地址" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "未找到使用中帳號的電子郵件。正在將您登出。" + }, "deviceTrusted": { "message": "裝置已信任" }, @@ -2935,7 +3092,7 @@ "message": "子選單" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -2993,16 +3150,16 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "使用瀏覽器啟動 Duo" }, "importFormatError": { "message": "資料格式不正確。請檢查匯入檔案後再試一次。" @@ -3017,7 +3174,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3156,7 +3313,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "成功" }, "troubleshooting": { "message": "疑難排解" @@ -3174,13 +3331,13 @@ "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": { @@ -3190,7 +3347,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3200,11 +3357,11 @@ } }, "back": { - "message": "Back", + "message": "返回", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "移除 $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3214,18 +3371,108 @@ } }, "data": { - "message": "Data" + "message": "資料" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "無法找到可用於 SSO 登入的空閒連接埠。" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "由於未在 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" + }, + "authorize": { + "message": "授權" + }, + "deny": { + "message": "拒絕" + }, + "sshkeyApprovalTitle": { + "message": "確認 SSH 密鑰使用" + }, + "sshkeyApprovalMessageInfix": { + "message": "正在請求存取權限到" + }, + "unknownApplication": { + "message": "應用程式" + }, + "sshKeyPasswordUnsupported": { + "message": "匯入密碼保護的 SSH 密鑰暫時還未支援" + }, + "invalidSshKey": { + "message": "SSH 密鑰不正確" + }, + "sshKeyTypeUnsupported": { + "message": "SSH 密鑰類型不支援" + }, + "importSshKeyFromClipboard": { + "message": "從剪貼簿中匯入密鑰" + }, + "sshKeyPasted": { + "message": "SSH 密鑰成功匯入" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "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": "更改帳號電子郵件位址" } } diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index f869898d57b..01d84a8f769 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1,6 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as path from "path"; -import { app, ipcMain } from "electron"; +import { app } from "electron"; import { Subject, firstValueFrom } from "rxjs"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; @@ -26,8 +28,9 @@ import { DefaultBiometricStateService } from "@bitwarden/key-management"; /* eslint-enable import/no-restricted-paths */ import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service"; -import { BiometricsRendererIPCListener } from "./key-management/biometrics/biometric.renderer-ipc.listener"; -import { BiometricsService, DesktopBiometricsService } from "./key-management/biometrics/index"; +import { DesktopBiometricsService } from "./key-management/biometrics/desktop.biometrics.service"; +import { MainBiometricsIPCListener } from "./key-management/biometrics/main-biometrics-ipc.listener"; +import { MainBiometricsService } from "./key-management/biometrics/main-biometrics.service"; import { MenuMain } from "./main/menu/menu.main"; import { MessagingMain } from "./main/messaging.main"; import { NativeMessagingMain } from "./main/native-messaging.main"; @@ -35,10 +38,12 @@ import { PowerMonitorMain } from "./main/power-monitor.main"; import { TrayMain } from "./main/tray.main"; import { UpdaterMain } from "./main/updater.main"; import { WindowMain } from "./main/window.main"; +import { NativeAutofillMain } from "./platform/main/autofill/native-autofill.main"; import { ClipboardMain } from "./platform/main/clipboard.main"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; import { MainSshAgentService } from "./platform/main/main-ssh-agent.service"; +import { VersionMain } from "./platform/main/version.main"; import { DesktopSettingsService } from "./platform/services/desktop-settings.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; import { ElectronStorageService } from "./platform/services/electron-storage.service"; @@ -57,7 +62,7 @@ export class Main { messagingService: MessageSender; environmentService: DefaultEnvironmentService; desktopCredentialStorageListener: DesktopCredentialStorageListener; - biometricsRendererIPCListener: BiometricsRendererIPCListener; + mainBiometricsIpcListener: MainBiometricsIPCListener; desktopSettingsService: DesktopSettingsService; mainCryptoFunctionService: MainCryptoFunctionService; migrationRunner: MigrationRunner; @@ -71,7 +76,9 @@ export class Main { biometricsService: DesktopBiometricsService; nativeMessagingMain: NativeMessagingMain; clipboardMain: ClipboardMain; + nativeAutofillMain: NativeAutofillMain; desktopAutofillSettingsService: DesktopAutofillSettingsService; + versionMain: VersionMain; sshAgentService: MainSshAgentService; constructor() { @@ -171,6 +178,15 @@ export class Main { this.desktopSettingsService = new DesktopSettingsService(stateProvider); const biometricStateService = new DefaultBiometricStateService(stateProvider); + this.biometricsService = new MainBiometricsService( + this.i18nService, + this.windowMain, + this.logService, + this.messagingService, + process.platform, + biometricStateService, + ); + this.windowMain = new WindowMain( biometricStateService, this.logService, @@ -181,7 +197,6 @@ export class Main { ); this.messagingMain = new MessagingMain(this, this.desktopSettingsService); this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain); - this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.desktopSettingsService); const messageSubject = new Subject>>(); this.messagingService = MessageSender.combine( @@ -199,6 +214,8 @@ export class Main { }); }); + this.versionMain = new VersionMain(this.windowMain); + this.powerMonitorMain = new PowerMonitorMain(this.messagingService, this.logService); this.menuMain = new MenuMain( this.i18nService, @@ -207,24 +224,22 @@ export class Main { this.windowMain, this.updaterMain, this.desktopSettingsService, + this.versionMain, ); - this.biometricsService = new BiometricsService( - this.i18nService, + this.trayMain = new TrayMain( this.windowMain, - this.logService, - this.messagingService, - process.platform, + this.i18nService, + this.desktopSettingsService, biometricStateService, + this.biometricsService, ); this.desktopCredentialStorageListener = new DesktopCredentialStorageListener( "Bitwarden", - this.biometricsService, this.logService, ); - this.biometricsRendererIPCListener = new BiometricsRendererIPCListener( - "Bitwarden", + this.mainBiometricsIpcListener = new MainBiometricsIPCListener( this.biometricsService, this.logService, ); @@ -242,20 +257,18 @@ export class Main { this.clipboardMain = new ClipboardMain(); this.clipboardMain.init(); - ipcMain.handle("sshagent.init", async (event: any, message: any) => { - if (this.sshAgentService == null) { - this.sshAgentService = new MainSshAgentService(this.logService, this.messagingService); - this.sshAgentService.init(); - } - }); + this.sshAgentService = new MainSshAgentService(this.logService, this.messagingService); new EphemeralValueStorageService(); new SSOLocalhostCallbackService(this.environmentService, this.messagingService); + + this.nativeAutofillMain = new NativeAutofillMain(this.logService, this.windowMain); + void this.nativeAutofillMain.init(); } bootstrap() { this.desktopCredentialStorageListener.init(); - this.biometricsRendererIPCListener.init(); + this.mainBiometricsIpcListener.init(); // Run migrations first, then other things this.migrationRunner.run().then( async () => { diff --git a/apps/desktop/src/main/menu/menu.about.ts b/apps/desktop/src/main/menu/menu.about.ts index 50e125b3d6c..bd82d73fcb4 100644 --- a/apps/desktop/src/main/menu/menu.about.ts +++ b/apps/desktop/src/main/menu/menu.about.ts @@ -2,6 +2,7 @@ import { BrowserWindow, clipboard, dialog, MenuItemConstructorOptions } from "el import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { VersionMain } from "../../platform/main/version.main"; import { isMacAppStore, isSnapStore, isWindowsStore } from "../../utils"; import { UpdaterMain } from "../updater.main"; @@ -22,17 +23,20 @@ export class AboutMenu implements IMenubarMenu { private readonly _updater: UpdaterMain; private readonly _window: BrowserWindow; private readonly _version: string; + private readonly _versionMain: VersionMain; constructor( i18nService: I18nService, version: string, window: BrowserWindow, updater: UpdaterMain, + versionMain: VersionMain, ) { this._i18nService = i18nService; this._updater = updater; this._version = version; this._window = window; + this._versionMain = versionMain; } private get separator(): MenuItemConstructorOptions { @@ -53,8 +57,11 @@ export class AboutMenu implements IMenubarMenu { id: "aboutBitwarden", label: this.localize("aboutBitwarden"), click: async () => { + const sdkVersion = await this._versionMain.sdkVersion(); const aboutInformation = this.localize("version", this._version) + + "\nSDK " + + sdkVersion + "\nShell " + process.versions.electron + "\nRenderer " + diff --git a/apps/desktop/src/main/menu/menu.main.ts b/apps/desktop/src/main/menu/menu.main.ts index 9a63d389b59..eafadf3bfb5 100644 --- a/apps/desktop/src/main/menu/menu.main.ts +++ b/apps/desktop/src/main/menu/menu.main.ts @@ -5,6 +5,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { VersionMain } from "../../platform/main/version.main"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { UpdaterMain } from "../updater.main"; import { WindowMain } from "../window.main"; @@ -22,6 +23,7 @@ export class MenuMain { private windowMain: WindowMain, private updaterMain: UpdaterMain, private desktopSettingsService: DesktopSettingsService, + private versionMain: VersionMain, ) {} async init() { @@ -44,6 +46,7 @@ export class MenuMain { await this.getWebVaultUrl(), app.getVersion(), await firstValueFrom(this.desktopSettingsService.hardwareAcceleration$), + this.versionMain, updateRequest, ).menu, ); diff --git a/apps/desktop/src/main/menu/menu.updater.ts b/apps/desktop/src/main/menu/menu.updater.ts index 170804cae25..6f82a78384f 100644 --- a/apps/desktop/src/main/menu/menu.updater.ts +++ b/apps/desktop/src/main/menu/menu.updater.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class MenuUpdateRequest { activeUserId: string; accounts: { [userId: string]: MenuAccount }; diff --git a/apps/desktop/src/main/menu/menu.view.ts b/apps/desktop/src/main/menu/menu.view.ts index c1553bdf088..962c57fdb60 100644 --- a/apps/desktop/src/main/menu/menu.view.ts +++ b/apps/desktop/src/main/menu/menu.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MenuItemConstructorOptions } from "electron"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -74,7 +76,7 @@ export class ViewMenu implements IMenubarMenu { private get passwordHistory(): MenuItemConstructorOptions { return { id: "passwordHistory", - label: this.localize("passwordHistory"), + label: this.localize("generatorHistory"), click: () => this.sendMessage("openPasswordHistory"), enabled: !this._isLocked, }; diff --git a/apps/desktop/src/main/menu/menu.window.ts b/apps/desktop/src/main/menu/menu.window.ts index 02cc6c6f8c9..745a12224fe 100644 --- a/apps/desktop/src/main/menu/menu.window.ts +++ b/apps/desktop/src/main/menu/menu.window.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MenuItemConstructorOptions } from "electron"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/apps/desktop/src/main/menu/menubar.ts b/apps/desktop/src/main/menu/menubar.ts index b71774c5afe..825afdaa1e8 100644 --- a/apps/desktop/src/main/menu/menubar.ts +++ b/apps/desktop/src/main/menu/menubar.ts @@ -1,8 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Menu, MenuItemConstructorOptions } from "electron"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { VersionMain } from "../../platform/main/version.main"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { isMac } from "../../utils"; import { UpdaterMain } from "../updater.main"; @@ -54,6 +57,7 @@ export class Menubar { webVaultUrl: string, appVersion: string, hardwareAccelerationEnabled: boolean, + versionMain: VersionMain, updateRequest?: MenuUpdateRequest, ) { let isLocked = true; @@ -96,7 +100,7 @@ export class Menubar { desktopSettingsService, webVaultUrl, hardwareAccelerationEnabled, - new AboutMenu(i18nService, appVersion, windowMain.win, updaterMain), + new AboutMenu(i18nService, appVersion, windowMain.win, updaterMain, versionMain), ), ]; diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index 68b1597ac45..0d86a7234e6 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 16594792f71..107d546811c 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { existsSync, promises as fs } from "fs"; import { homedir, userInfo } from "os"; import * as path from "path"; @@ -93,11 +95,23 @@ export class NativeMessagingMain { break; } case ipc.IpcMessageType.Message: - this.windowMain.win.webContents.send("nativeMessaging", JSON.parse(msg.message)); + try { + const msgJson = JSON.parse(msg.message); + this.logService.debug("Native messaging message:", msgJson); + this.windowMain.win?.webContents.send("nativeMessaging", msgJson); + } catch (e) { + this.logService.warning("Error processing message:", e, msg.message); + } + break; + + default: + this.logService.warning("Unknown message type:", msg.kind, msg.message); break; } }); + this.logService.info("Native messaging server started at:", this.ipcServer.getPath()); + ipcMain.on("nativeMessagingReply", (event, msg) => { if (msg != null) { this.send(msg); @@ -110,6 +124,7 @@ export class NativeMessagingMain { } send(message: object) { + this.logService.debug("Native messaging reply:", message); this.ipcServer?.send(JSON.stringify(message)); } @@ -390,6 +405,8 @@ export class NativeMessagingMain { this.logService.info(`Error reading preferences: ${e}`); } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Browser is not installed, we can just skip it } diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index 8450a653222..9fa7fe6143f 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -1,9 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as path from "path"; import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron"; import { firstValueFrom } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { BiometricStateService, BiometricsService } from "@bitwarden/key-management"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; @@ -21,6 +24,8 @@ export class TrayMain { private windowMain: WindowMain, private i18nService: I18nService, private desktopSettingsService: DesktopSettingsService, + private biometricsStateService: BiometricStateService, + private biometricService: BiometricsService, ) { if (process.platform === "win32") { this.icon = path.join(__dirname, "/images/icon.ico"); @@ -62,15 +67,18 @@ export class TrayMain { } setupWindowListeners(win: BrowserWindow) { - win.on("minimize", async (e: Event) => { + win.on("minimize", async () => { if (await firstValueFrom(this.desktopSettingsService.minimizeToTray$)) { - e.preventDefault(); // 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.hideToTray(); } }); + win.on("restore", async () => { + await this.biometricService.setShouldAutopromptNow(true); + }); + win.on("close", async (e: Event) => { if (await firstValueFrom(this.desktopSettingsService.closeToTray$)) { if (!this.windowMain.isQuitting) { diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 6d42e519d82..8fe17772072 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { once } from "node:events"; import * as path from "path"; import * as url from "url"; @@ -12,15 +14,7 @@ import { BiometricStateService } from "@bitwarden/key-management"; import { WindowState } from "../platform/models/domain/window-state"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; -import { - cleanUserAgent, - isDev, - isLinux, - isMac, - isMacAppStore, - isSnapStore, - isWindows, -} from "../utils"; +import { cleanUserAgent, isDev, isLinux, isMac, isMacAppStore, isWindows } from "../utils"; const mainWindowSizeKey = "mainWindowSize"; const WindowEventHandlingDelay = 100; @@ -84,7 +78,7 @@ export class WindowMain { return new Promise((resolve, reject) => { try { - if (!isMacAppStore() && !isSnapStore()) { + if (!isMacAppStore()) { const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); diff --git a/apps/desktop/src/models/native-messaging/legacy-message.ts b/apps/desktop/src/models/native-messaging/legacy-message.ts index a2bcf2aa7e5..99047cdcd34 100644 --- a/apps/desktop/src/models/native-messaging/legacy-message.ts +++ b/apps/desktop/src/models/native-messaging/legacy-message.ts @@ -1,5 +1,6 @@ export type LegacyMessage = { command: string; + messageId: number; userId?: string; timestamp?: number; diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 21075252981..d878e1af2aa 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,16 +1,15 @@ { "name": "@bitwarden/desktop", - "version": "2024.11.0", + "version": "2025.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.11.0", + "version": "2025.1.2", "license": "GPL-3.0", "dependencies": { - "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.41.1" + "@bitwarden/desktop-napi": "file:../desktop_native/napi" } }, "../desktop_native/napi": { @@ -24,50 +23,6 @@ "node_modules/@bitwarden/desktop-napi": { "resolved": "../desktop_native/napi", "link": true - }, - "node_modules/@phc/format": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", - "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/argon2": { - "version": "0.41.1", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", - "integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@phc/format": "^1.0.0", - "node-addon-api": "^8.1.0", - "node-gyp-build": "^4.8.1" - }, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/node-addon-api": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.1.tgz", - "integrity": "sha512-vmEOvxwiH8tlOcv4SyE8RH34rI5/nWVaigUeAUPawC6f0+HoDthwI0vkMu4tbtsZrXq6QXFfrkhjofzKEs5tpA==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } } } } diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 3f4bb0fc0cf..08bdd745063 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2024.11.0", + "version": "2025.1.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", @@ -12,7 +12,6 @@ "url": "git+https://github.com/bitwarden/clients.git" }, "dependencies": { - "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.41.1" + "@bitwarden/desktop-napi": "file:../desktop_native/napi" } } diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 62200962dca..0443034f551 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -10,8 +10,8 @@ import { DialogModule, FormFieldModule, IconButtonModule, + DialogService, } from "@bitwarden/components"; -import { DialogService } from "@bitwarden/components/src/dialog"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface ApproveSshRequestParams { diff --git a/apps/desktop/src/platform/flags.ts b/apps/desktop/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/desktop/src/platform/flags.ts +++ b/apps/desktop/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/desktop/src/platform/main/autofill/command.ts b/apps/desktop/src/platform/main/autofill/command.ts new file mode 100644 index 00000000000..a8b5548052b --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/command.ts @@ -0,0 +1,23 @@ +import { NativeAutofillStatusCommand } from "./status.command"; +import { NativeAutofillSyncCommand } from "./sync.command"; + +export type CommandDefinition = { + namespace: string; + name: string; + input: Record; + output: Record; +}; + +export type CommandOutput = + | { + type: "error"; + error: string; + } + | { type: "success"; value: SuccessOutput }; + +export type IpcCommandInvoker = ( + params: C["input"], +) => Promise>; + +/** A list of all available commands */ +export type Command = NativeAutofillSyncCommand | NativeAutofillStatusCommand; diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts new file mode 100644 index 00000000000..1465831340f --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -0,0 +1,106 @@ +import { ipcMain } from "electron"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { autofill } from "@bitwarden/desktop-napi"; + +import { WindowMain } from "../../../main/window.main"; + +import { CommandDefinition } from "./command"; + +export type RunCommandParams = { + namespace: C["namespace"]; + command: C["name"]; + params: C["input"]; +}; + +export type RunCommandResult = C["output"]; + +export class NativeAutofillMain { + private ipcServer: autofill.IpcServer | null; + + constructor( + private logService: LogService, + private windowMain: WindowMain, + ) {} + + async init() { + ipcMain.handle( + "autofill.runCommand", + ( + _event: any, + params: RunCommandParams, + ): Promise> => { + return this.runCommand(params); + }, + ); + + this.ipcServer = await autofill.IpcServer.listen( + "autofill", + // RegistrationCallback + (error, clientId, sequenceNumber, request) => { + if (error) { + this.logService.error("autofill.IpcServer.registration", error); + return; + } + this.windowMain.win.webContents.send("autofill.passkeyRegistration", { + clientId, + sequenceNumber, + request, + }); + }, + // AssertionCallback + (error, clientId, sequenceNumber, request) => { + if (error) { + this.logService.error("autofill.IpcServer.assertion", error); + return; + } + this.windowMain.win.webContents.send("autofill.passkeyAssertion", { + clientId, + sequenceNumber, + request, + }); + }, + ); + + ipcMain.on("autofill.completePasskeyRegistration", (event, data) => { + this.logService.warning("autofill.completePasskeyRegistration", data); + const { clientId, sequenceNumber, response } = data; + this.ipcServer.completeRegistration(clientId, sequenceNumber, response); + }); + + ipcMain.on("autofill.completePasskeyAssertion", (event, data) => { + this.logService.warning("autofill.completePasskeyAssertion", data); + const { clientId, sequenceNumber, response } = data; + this.ipcServer.completeAssertion(clientId, sequenceNumber, response); + }); + + ipcMain.on("autofill.completeError", (event, data) => { + this.logService.warning("autofill.completeError", data); + const { clientId, sequenceNumber, error } = data; + this.ipcServer.completeAssertion(clientId, sequenceNumber, error); + }); + } + + private async runCommand( + command: RunCommandParams, + ): Promise> { + try { + const result = await autofill.runCommand(JSON.stringify(command)); + const parsed = JSON.parse(result) as RunCommandResult; + + if (parsed.type === "error") { + this.logService.error(`Error running autofill command '${command.command}':`, parsed.error); + } + + return parsed; + } catch (e) { + this.logService.error(`Error running autofill command '${command.command}':`, e); + + if (e instanceof Error) { + return { type: "error", error: e.stack ?? String(e) } as RunCommandResult; + } + + return { type: "error", error: String(e) } as RunCommandResult; + } + } +} diff --git a/apps/desktop/src/platform/main/autofill/status.command.ts b/apps/desktop/src/platform/main/autofill/status.command.ts new file mode 100644 index 00000000000..b6c0943fa68 --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/status.command.ts @@ -0,0 +1,20 @@ +import { CommandDefinition, CommandOutput } from "./command"; + +export interface NativeAutofillStatusCommand extends CommandDefinition { + name: "status"; + input: NativeAutofillStatusParams; + output: NativeAutofillStatusResult; +} + +export type NativeAutofillStatusParams = Record; + +export type NativeAutofillStatusResult = CommandOutput<{ + support: { + fido2: boolean; + password: boolean; + incrementalUpdates: boolean; + }; + state: { + enabled: boolean; + }; +}>; diff --git a/apps/desktop/src/platform/main/autofill/sync.command.ts b/apps/desktop/src/platform/main/autofill/sync.command.ts new file mode 100644 index 00000000000..dc3b12383ef --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/sync.command.ts @@ -0,0 +1,37 @@ +import { CommandDefinition, CommandOutput } from "./command"; + +export interface NativeAutofillSyncCommand extends CommandDefinition { + name: "sync"; + input: NativeAutofillSyncParams; + output: NativeAutofillSyncResult; +} + +export type NativeAutofillSyncParams = { + credentials: NativeAutofillCredential[]; +}; + +export type NativeAutofillCredential = + | NativeAutofillFido2Credential + | NativeAutofillPasswordCredential; + +export type NativeAutofillFido2Credential = { + type: "fido2"; + cipherId: string; + rpId: string; + userName: string; + /** Should be Base64URL-encoded binary data */ + credentialId: string; + /** Should be Base64URL-encoded binary data */ + userHandle: string; +}; + +export type NativeAutofillPasswordCredential = { + type: "password"; + cipherId: string; + uri: string; + username: string; +}; + +export type NativeAutofillSyncResult = CommandOutput<{ + added: number; +}>; diff --git a/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts b/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts index 91aa2a7ad9f..ca4d9a2d3ca 100644 --- a/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts +++ b/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts @@ -1,17 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ipcMain } from "electron"; -import { BiometricKey } from "@bitwarden/common/auth/types/biometric-key"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { passwords } from "@bitwarden/desktop-napi"; -import { DesktopBiometricsService } from "../../key-management/biometrics/index"; - -const AuthRequiredSuffix = "_biometric"; - export class DesktopCredentialStorageListener { constructor( private serviceName: string, - private biometricService: DesktopBiometricsService, private logService: ConsoleLogService, ) {} @@ -52,13 +48,7 @@ export class DesktopCredentialStorageListener { // Gracefully handle old keytar values, and if detected updated the entry to the proper format private async getPassword(serviceName: string, key: string, keySuffix: string) { - let val: string; - // todo: remove this when biometrics has been migrated to desktop_native - if (keySuffix === AuthRequiredSuffix) { - val = (await this.biometricService.getBiometricKey(serviceName, key)) ?? null; - } else { - val = await passwords.getPassword(serviceName, key); - } + const val = await passwords.getPassword(serviceName, key); try { JSON.parse(val); @@ -70,25 +60,10 @@ export class DesktopCredentialStorageListener { } private async setPassword(serviceName: string, key: string, value: string, keySuffix: string) { - if (keySuffix === AuthRequiredSuffix) { - const valueObj = JSON.parse(value) as BiometricKey; - await this.biometricService.setEncryptionKeyHalf({ - service: serviceName, - key, - value: valueObj?.clientEncKeyHalf, - }); - // Value is usually a JSON string, but we need to pass the key half as well, so we re-stringify key here. - await this.biometricService.setBiometricKey(serviceName, key, JSON.stringify(valueObj?.key)); - } else { - await passwords.setPassword(serviceName, key, value); - } + await passwords.setPassword(serviceName, key, value); } private async deletePassword(serviceName: string, key: string, keySuffix: string) { - if (keySuffix === AuthRequiredSuffix) { - await this.biometricService.deleteBiometricKey(serviceName, key); - } else { - await passwords.deletePassword(serviceName, key); - } + await passwords.deletePassword(serviceName, key); } } diff --git a/apps/desktop/src/platform/main/main-crypto-function.service.ts b/apps/desktop/src/platform/main/main-crypto-function.service.ts index 848e33113e5..2fc3fde1db2 100644 --- a/apps/desktop/src/platform/main/main-crypto-function.service.ts +++ b/apps/desktop/src/platform/main/main-crypto-function.service.ts @@ -1,6 +1,7 @@ import { ipcMain } from "electron"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { crypto } from "@bitwarden/desktop-napi"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; export class MainCryptoFunctionService @@ -13,16 +14,16 @@ export class MainCryptoFunctionService async ( event, opts: { - password: string | Uint8Array; - salt: string | Uint8Array; + password: Uint8Array; + salt: Uint8Array; iterations: number; memory: number; parallelism: number; }, ) => { - return await this.argon2( - opts.password, - opts.salt, + return await crypto.argon2( + Buffer.from(opts.password), + Buffer.from(opts.salt), opts.iterations, opts.memory, opts.parallelism, diff --git a/apps/desktop/src/platform/main/main-ssh-agent.service.ts b/apps/desktop/src/platform/main/main-ssh-agent.service.ts index c8227e5b6a5..cc4565f27f4 100644 --- a/apps/desktop/src/platform/main/main-ssh-agent.service.ts +++ b/apps/desktop/src/platform/main/main-ssh-agent.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ipcMain } from "electron"; import { concatMap, delay, filter, firstValueFrom, from, race, take, timer } from "rxjs"; @@ -22,12 +24,30 @@ export class MainSshAgentService { constructor( private logService: LogService, private messagingService: MessagingService, - ) {} + ) { + ipcMain.handle( + "sshagent.importkey", + async ( + event: any, + { privateKey, password }: { privateKey: string; password?: string }, + ): Promise => { + return sshagent.importKey(privateKey, password); + }, + ); + + ipcMain.handle("sshagent.init", async (event: any, message: any) => { + this.init(); + }); + + ipcMain.handle("sshagent.isloaded", async (event: any) => { + return this.agentState != null; + }); + } init() { // handle sign request passing to UI sshagent - .serve(async (err: Error, cipherId: string) => { + .serve(async (err: Error, cipherId: string, isListRequest: boolean, processName: string) => { // clear all old (> SIGN_TIMEOUT) requests this.requestResponses = this.requestResponses.filter( (response) => response.timestamp > new Date(Date.now() - this.SIGN_TIMEOUT), @@ -37,7 +57,9 @@ export class MainSshAgentService { const id_for_this_request = this.request_id; this.messagingService.send("sshagent.signrequest", { cipherId, + isListRequest, requestId: id_for_this_request, + processName, }); const result = await firstValueFrom( @@ -79,7 +101,7 @@ export class MainSshAgentService { ipcMain.handle( "sshagent.setkeys", async (event: any, keys: { name: string; privateKey: string; cipherId: string }[]) => { - if (this.agentState != null) { + if (this.agentState != null && (await sshagent.isRunning(this.agentState))) { sshagent.setKeys(this.agentState, keys); } }, @@ -90,26 +112,17 @@ export class MainSshAgentService { this.requestResponses.push({ requestId, accepted, timestamp: new Date() }); }, ); - ipcMain.handle( - "sshagent.generatekey", - async (event: any, { keyAlgorithm }: { keyAlgorithm: string }): Promise => { - return await sshagent.generateKeypair(keyAlgorithm); - }, - ); - ipcMain.handle( - "sshagent.importkey", - async ( - event: any, - { privateKey, password }: { privateKey: string; password?: string }, - ): Promise => { - return sshagent.importKey(privateKey, password); - }, - ); ipcMain.handle("sshagent.lock", async (event: any) => { - if (this.agentState != null) { + if (this.agentState != null && (await sshagent.isRunning(this.agentState))) { sshagent.lock(this.agentState); } }); + + ipcMain.handle("sshagent.clearkeys", async (event: any) => { + if (this.agentState != null) { + sshagent.clearKeys(this.agentState); + } + }); } } diff --git a/apps/desktop/src/platform/main/version.main.ts b/apps/desktop/src/platform/main/version.main.ts new file mode 100644 index 00000000000..1c39b641d27 --- /dev/null +++ b/apps/desktop/src/platform/main/version.main.ts @@ -0,0 +1,17 @@ +import { ipcMain } from "electron"; + +import { WindowMain } from "../../main/window.main"; + +export class VersionMain { + constructor(private windowMain: WindowMain) {} + + sdkVersion() { + const timeout = new Promise((resolve) => setTimeout(() => resolve("Timeout error"), 1000)); + const version = new Promise((resolve) => { + ipcMain.once("sdkVersion", (_, version) => resolve(version)); + this.windowMain.win.webContents.send("sdkVersion"); + }); + + return Promise.race([timeout, version]); + } +} diff --git a/apps/desktop/src/platform/models/domain/window-state.ts b/apps/desktop/src/platform/models/domain/window-state.ts index aba1cdb470e..00230319972 100644 --- a/apps/desktop/src/platform/models/domain/window-state.ts +++ b/apps/desktop/src/platform/models/domain/window-state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class WindowState { width?: number; height?: number; diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 35caeff27c8..a37274677ff 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -11,7 +11,15 @@ import { Message, UnencryptedMessageResponse, } from "../models/native-messaging"; -import { isAppImage, isDev, isFlatpak, isMacAppStore, isSnapStore, isWindowsStore } from "../utils"; +import { + allowBrowserintegrationOverride, + isAppImage, + isDev, + isFlatpak, + isMacAppStore, + isSnapStore, + isWindowsStore, +} from "../utils"; import { ClipboardWriteMessage } from "./types/clipboard"; @@ -50,12 +58,12 @@ const sshAgent = { signRequestResponse: async (requestId: number, accepted: boolean) => { await ipcRenderer.invoke("sshagent.signrequestresponse", { requestId, accepted }); }, - generateKey: async (keyAlgorithm: string): Promise => { - return await ipcRenderer.invoke("sshagent.generatekey", { keyAlgorithm }); - }, lock: async () => { return await ipcRenderer.invoke("sshagent.lock"); }, + clearKeys: async () => { + return await ipcRenderer.invoke("sshagent.clearkeys"); + }, importKey: async (key: string, password: string): Promise => { const res = await ipcRenderer.invoke("sshagent.importkey", { privateKey: key, @@ -63,6 +71,9 @@ const sshAgent = { }); return res; }, + isLoaded(): Promise { + return ipcRenderer.invoke("sshagent.isloaded"); + }, }; const powermonitor = { @@ -76,6 +87,7 @@ const nativeMessaging = { }, sendMessage: (message: { appId: string; + messageId?: number; command?: string; sharedSecret?: string; message?: EncString; @@ -96,8 +108,8 @@ const nativeMessaging = { const crypto = { argon2: ( - password: string | Uint8Array, - salt: string | Uint8Array, + password: Uint8Array, + salt: Uint8Array, iterations: number, memory: number, parallelism: number, @@ -122,6 +134,13 @@ const localhostCallbackService = { export default { versions: { app: (): Promise => ipcRenderer.invoke("appVersion"), + registerSdkVersionProvider: (provide: (resolve: (version: string) => void) => void) => { + const resolve = (version: string) => ipcRenderer.send("sdkVersion", version); + + ipcRenderer.on("sdkVersion", () => { + provide(resolve); + }); + }, }, deviceType: deviceType(), isDev: isDev(), @@ -130,6 +149,7 @@ export default { isFlatpak: isFlatpak(), isSnapStore: isSnapStore(), isAppImage: isAppImage(), + allowBrowserintegrationOverride: allowBrowserintegrationOverride(), reloadProcess: () => ipcRenderer.send("reload-process"), focusWindow: () => ipcRenderer.send("window-focus"), hideWindow: () => ipcRenderer.send("window-hide"), diff --git a/apps/desktop/src/platform/services/electron-key.service.spec.ts b/apps/desktop/src/platform/services/electron-key.service.spec.ts deleted file mode 100644 index 8705f1fba6e..00000000000 --- a/apps/desktop/src/platform/services/electron-key.service.spec.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; -import { mock } from "jest-mock-extended"; - -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { makeEncString } from "@bitwarden/common/spec"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; -import { BiometricStateService } from "@bitwarden/key-management"; - -import { - FakeAccountService, - mockAccountServiceWith, -} from "../../../../../libs/common/spec/fake-account-service"; - -import { ElectronKeyService } from "./electron-key.service"; - -describe("electronKeyService", () => { - let sut: ElectronKeyService; - - const pinService = mock(); - const keyGenerationService = mock(); - const cryptoFunctionService = mock(); - const encryptService = mock(); - const platformUtilService = mock(); - const logService = mock(); - const stateService = mock(); - let masterPasswordService: FakeMasterPasswordService; - let accountService: FakeAccountService; - let stateProvider: FakeStateProvider; - const biometricStateService = mock(); - const kdfConfigService = mock(); - - const mockUserId = "mock user id" as UserId; - - beforeEach(() => { - accountService = mockAccountServiceWith("userId" as UserId); - masterPasswordService = new FakeMasterPasswordService(); - stateProvider = new FakeStateProvider(accountService); - - sut = new ElectronKeyService( - pinService, - masterPasswordService, - keyGenerationService, - cryptoFunctionService, - encryptService, - platformUtilService, - logService, - stateService, - accountService, - stateProvider, - biometricStateService, - kdfConfigService, - ); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("setUserKey", () => { - let mockUserKey: UserKey; - - beforeEach(() => { - const mockRandomBytes = new Uint8Array(64) as CsprngArray; - mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; - }); - - describe("Biometric Key refresh", () => { - const encClientKeyHalf = makeEncString(); - const decClientKeyHalf = "decrypted client key half"; - - beforeEach(() => { - encClientKeyHalf.decrypt = jest.fn().mockResolvedValue(decClientKeyHalf); - }); - - it("sets a Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => { - biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); - platformUtilService.supportsSecureStorage.mockReturnValue(true); - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); - biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(encClientKeyHalf); - - await sut.setUserKey(mockUserKey, mockUserId); - - expect(stateService.setUserKeyBiometric).toHaveBeenCalledWith( - expect.objectContaining({ key: expect.any(String), clientEncKeyHalf: decClientKeyHalf }), - { - userId: mockUserId, - }, - ); - }); - - it("clears the Biometric key if getBiometricUnlock is false or the platform does not support secure storage", async () => { - biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); - platformUtilService.supportsSecureStorage.mockReturnValue(false); - - await sut.setUserKey(mockUserKey, mockUserId); - - expect(stateService.setUserKeyBiometric).toHaveBeenCalledWith(null, { - userId: mockUserId, - }); - }); - }); - }); -}); diff --git a/apps/desktop/src/platform/services/electron-key.service.ts b/apps/desktop/src/platform/services/electron-key.service.ts index f7cfb3cf92f..0db634375ef 100644 --- a/apps/desktop/src/platform/services/electron-key.service.ts +++ b/apps/desktop/src/platform/services/electron-key.service.ts @@ -1,8 +1,7 @@ -import { firstValueFrom } from "rxjs"; - +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -12,12 +11,17 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CsprngString } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; -import { DefaultKeyService, BiometricStateService } from "@bitwarden/key-management"; +import { + KdfConfigService, + DefaultKeyService, + BiometricStateService, +} from "@bitwarden/key-management"; + +import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; export class ElectronKeyService extends DefaultKeyService { constructor( @@ -33,6 +37,7 @@ export class ElectronKeyService extends DefaultKeyService { stateProvider: StateProvider, private biometricStateService: BiometricStateService, kdfConfigService: KdfConfigService, + private biometricService: DesktopBiometricsService, ) { super( pinService, @@ -50,19 +55,10 @@ export class ElectronKeyService extends DefaultKeyService { } override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - return await this.stateService.hasUserKeyBiometric({ userId: userId }); - } return super.hasUserKeyStored(keySuffix, userId); } override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - await this.stateService.setUserKeyBiometric(null, { userId: userId }); - await this.biometricStateService.removeEncryptedClientKeyHalf(userId); - await this.clearDeprecatedKeys(KeySuffixOptions.Biometric, userId); - return; - } // 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 await super.clearStoredUserKey(keySuffix, userId); @@ -71,52 +67,35 @@ export class ElectronKeyService extends DefaultKeyService { protected override async storeAdditionalKeys(key: UserKey, userId: UserId) { await super.storeAdditionalKeys(key, userId); - const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId); - - if (storeBiometricKey) { - await this.storeBiometricKey(key, userId); - } else { - await this.stateService.setUserKeyBiometric(null, { userId: userId }); + if (await this.biometricStateService.getBiometricUnlockEnabled(userId)) { + await this.storeBiometricsProtectedUserKey(key, userId); } - await this.clearDeprecatedKeys(KeySuffixOptions.Biometric, userId); } protected override async getKeyFromStorage( keySuffix: KeySuffixOptions, userId?: UserId, ): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const userKey = await this.stateService.getUserKeyBiometric({ userId: userId }); - return userKey == null - ? null - : (new SymmetricCryptoKey(Utils.fromB64ToArray(userKey)) as UserKey); - } return await super.getKeyFromStorage(keySuffix, userId); } - protected async storeBiometricKey(key: UserKey, userId?: UserId): Promise { + protected async storeBiometricsProtectedUserKey( + userKey: UserKey, + userId?: UserId, + ): Promise { // May resolve to null, in which case no client key have is required - const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(key, userId); - await this.stateService.setUserKeyBiometric( - { key: key.keyB64, clientEncKeyHalf }, - { userId: userId }, - ); + // TODO: Move to windows implementation + const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId); + await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf); + await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64); } protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const biometricUnlockPromise = - userId == null - ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) - : this.biometricStateService.getBiometricUnlockEnabled(userId); - const biometricUnlock = await biometricUnlockPromise; - return biometricUnlock && this.platformUtilService.supportsSecureStorage(); - } return await super.shouldStoreKey(keySuffix, userId); } protected override async clearAllStoredUserKeys(userId?: UserId): Promise { - await this.clearStoredUserKey(KeySuffixOptions.Biometric, userId); + await this.biometricService.deleteBiometricUnlockKeyForUser(userId); await super.clearAllStoredUserKeys(userId); } @@ -130,18 +109,18 @@ export class ElectronKeyService extends DefaultKeyService { } // Retrieve existing key half if it exists - let biometricKey = await this.biometricStateService + let clientKeyHalf = await this.biometricStateService .getEncryptedClientKeyHalf(userId) .then((result) => result?.decrypt(null /* user encrypted */, userKey)) .then((result) => result as CsprngString); - if (biometricKey == null && userKey != null) { + if (clientKeyHalf == null && userKey != null) { // Set a key half if it doesn't exist const keyBytes = await this.cryptoFunctionService.randomBytes(32); - biometricKey = Utils.fromBufferToUtf8(keyBytes) as CsprngString; - const encKey = await this.encryptService.encrypt(biometricKey, userKey); + clientKeyHalf = Utils.fromBufferToUtf8(keyBytes) as CsprngString; + const encKey = await this.encryptService.encrypt(clientKeyHalf, userKey); await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId); } - return biometricKey; + return clientKeyHalf; } } diff --git a/apps/desktop/src/platform/services/electron-log.main.service.ts b/apps/desktop/src/platform/services/electron-log.main.service.ts index 0725de3dc9f..d7100b54825 100644 --- a/apps/desktop/src/platform/services/electron-log.main.service.ts +++ b/apps/desktop/src/platform/services/electron-log.main.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as path from "path"; import { ipcMain } from "electron"; diff --git a/apps/desktop/src/platform/services/electron-log.renderer.service.ts b/apps/desktop/src/platform/services/electron-log.renderer.service.ts index cea939f1609..c6445055ff2 100644 --- a/apps/desktop/src/platform/services/electron-log.renderer.service.ts +++ b/apps/desktop/src/platform/services/electron-log.renderer.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogLevelType } from "@bitwarden/common/platform/enums/log-level-type.enum"; import { ConsoleLogService as BaseLogService } from "@bitwarden/common/platform/services/console-log.service"; diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index 2808b74f097..b61d2a0c5e9 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ClientType, DeviceType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; diff --git a/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts b/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts index da328b0449e..f67b541def6 100644 --- a/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { throwError } from "rxjs"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; diff --git a/apps/desktop/src/platform/services/electron-storage.service.ts b/apps/desktop/src/platform/services/electron-storage.service.ts index 3fa9b2220c5..2d292d6537b 100644 --- a/apps/desktop/src/platform/services/electron-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import { ipcMain } from "electron"; diff --git a/apps/desktop/src/platform/services/ssh-agent.service.ts b/apps/desktop/src/platform/services/ssh-agent.service.ts index dd518a943b8..d4c7c5f460e 100644 --- a/apps/desktop/src/platform/services/ssh-agent.service.ts +++ b/apps/desktop/src/platform/services/ssh-agent.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable, OnDestroy } from "@angular/core"; import { catchError, @@ -5,9 +7,11 @@ import { concatMap, EMPTY, filter, + firstValueFrom, from, map, of, + skip, Subject, switchMap, takeUntil, @@ -17,6 +21,7 @@ import { withLatestFrom, } from "rxjs"; +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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -37,9 +42,11 @@ import { DesktopSettingsService } from "./desktop-settings.service"; }) export class SshAgentService implements OnDestroy { SSH_REFRESH_INTERVAL = 1000; - SSH_VAULT_UNLOCK_REQUEST_TIMEOUT = 1000 * 60; + SSH_VAULT_UNLOCK_REQUEST_TIMEOUT = 60_000; SSH_REQUEST_UNLOCK_POLLING_INTERVAL = 100; + private isFeatureFlagEnabled = false; + private destroy$ = new Subject(); constructor( @@ -52,112 +59,99 @@ export class SshAgentService implements OnDestroy { private i18nService: I18nService, private desktopSettingsService: DesktopSettingsService, private configService: ConfigService, + private accountService: AccountService, ) {} async init() { - const isSshAgentFeatureEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHAgent); - if (isSshAgentFeatureEnabled) { - await ipc.platform.sshAgent.init(); - - this.messageListener - .messages$(new CommandDefinition("sshagent.signrequest")) - .pipe( - withLatestFrom(this.authService.activeAccountStatus$), - // This switchMap handles unlocking the vault if it is locked: - // - If the vault is locked, we will wait for it to be unlocked. - // - If the vault is not unlocked within the timeout, we will abort the flow. - // - If the vault is unlocked, we will continue with the flow. - // switchMap is used here to prevent multiple requests from being processed at the same time, - // and will cancel the previous request if a new one is received. - switchMap(([message, status]) => { - if (status !== AuthenticationStatus.Unlocked) { - ipc.platform.focusWindow(); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("sshAgentUnlockRequired"), - }); - return this.authService.activeAccountStatus$.pipe( - filter((status) => status === AuthenticationStatus.Unlocked), - timeout(this.SSH_VAULT_UNLOCK_REQUEST_TIMEOUT), - catchError((error: unknown) => { - if (error instanceof TimeoutError) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("sshAgentUnlockTimeout"), - }); - const requestId = message.requestId as number; - // Abort flow by sending a false response. - // Returning an empty observable this will prevent the rest of the flow from executing - return from(ipc.platform.sshAgent.signRequestResponse(requestId, false)).pipe( - map(() => EMPTY), - ); - } - - throw error; - }), - map(() => message), - ); - } - - return of(message); - }), - // This switchMap handles fetching the ciphers from the vault. - switchMap((message) => - from(this.cipherService.getAllDecrypted()).pipe( - map((ciphers) => [message, ciphers] as const), - ), - ), - // This concatMap handles showing the dialog to approve the request. - concatMap(([message, decryptedCiphers]) => { - const cipherId = message.cipherId as string; - const requestId = message.requestId as number; - - if (decryptedCiphers === undefined) { - return of(false).pipe( - switchMap((result) => - ipc.platform.sshAgent.signRequestResponse(requestId, Boolean(result)), - ), - ); - } + this.configService + .getFeatureFlag$(FeatureFlag.SSHAgent) + .pipe( + concatMap(async (enabled) => { + this.isFeatureFlagEnabled = enabled; + if (!(await ipc.platform.sshAgent.isLoaded()) && enabled) { + await ipc.platform.sshAgent.init(); + } + }), + takeUntil(this.destroy$), + ) + .subscribe(); - const cipher = decryptedCiphers.find((cipher) => cipher.id == cipherId); + await this.initListeners(); + } + private async initListeners() { + this.messageListener + .messages$(new CommandDefinition("sshagent.signrequest")) + .pipe( + withLatestFrom(this.desktopSettingsService.sshAgentEnabled$), + concatMap(async ([message, enabled]) => { + if (!enabled) { + await ipc.platform.sshAgent.signRequestResponse(message.requestId as number, false); + } + return { message, enabled }; + }), + filter(({ enabled }) => enabled), + map(({ message }) => message), + withLatestFrom(this.authService.activeAccountStatus$), + // This switchMap handles unlocking the vault if it is locked: + // - If the vault is locked, we will wait for it to be unlocked. + // - If the vault is not unlocked within the timeout, we will abort the flow. + // - If the vault is unlocked, we will continue with the flow. + // switchMap is used here to prevent multiple requests from being processed at the same time, + // and will cancel the previous request if a new one is received. + switchMap(([message, status]) => { + if (status !== AuthenticationStatus.Unlocked) { ipc.platform.focusWindow(); - const dialogRef = ApproveSshRequestComponent.open( - this.dialogService, - cipher.name, - this.i18nService.t("unknownApplication"), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("sshAgentUnlockRequired"), + }); + return this.authService.activeAccountStatus$.pipe( + filter((status) => status === AuthenticationStatus.Unlocked), + timeout({ + first: this.SSH_VAULT_UNLOCK_REQUEST_TIMEOUT, + }), + catchError((error: unknown) => { + if (error instanceof TimeoutError) { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("sshAgentUnlockTimeout"), + }); + const requestId = message.requestId as number; + // Abort flow by sending a false response. + // Returning an empty observable this will prevent the rest of the flow from executing + return from(ipc.platform.sshAgent.signRequestResponse(requestId, false)).pipe( + map(() => EMPTY), + ); + } - return dialogRef.closed.pipe( - switchMap((result) => { - return ipc.platform.sshAgent.signRequestResponse(requestId, Boolean(result)); + throw error; }), + map(() => message), ); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - combineLatest([ - timer(0, this.SSH_REFRESH_INTERVAL), - this.desktopSettingsService.sshAgentEnabled$, - ]) - .pipe( - concatMap(async ([, enabled]) => { - if (!enabled) { - await ipc.platform.sshAgent.setKeys([]); - return; - } - - const ciphers = await this.cipherService.getAllDecrypted(); - if (ciphers == null) { - await ipc.platform.sshAgent.lock(); - return; - } + } + + return of(message); + }), + // This switchMap handles fetching the ciphers from the vault. + switchMap((message) => + from(this.cipherService.getAllDecrypted()).pipe( + map((ciphers) => [message, ciphers] as const), + ), + ), + // This concatMap handles showing the dialog to approve the request. + concatMap(async ([message, ciphers]) => { + const cipherId = message.cipherId as string; + const isListRequest = message.isListRequest as boolean; + const requestId = message.requestId as number; + let application = message.processName as string; + if (application == "") { + application = this.i18nService.t("unknownApplication"); + } + if (isListRequest) { const sshCiphers = ciphers.filter( (cipher) => cipher.type === CipherType.SshKey && !cipher.isDeleted, ); @@ -169,11 +163,112 @@ export class SshAgentService implements OnDestroy { }; }); await ipc.platform.sshAgent.setKeys(keys); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - } + await ipc.platform.sshAgent.signRequestResponse(requestId, true); + return; + } + + if (ciphers === undefined) { + ipc.platform.sshAgent + .signRequestResponse(requestId, false) + .catch((e) => this.logService.error("Failed to respond to SSH request", e)); + } + + const cipher = ciphers.find((cipher) => cipher.id == cipherId); + + ipc.platform.focusWindow(); + const dialogRef = ApproveSshRequestComponent.open( + this.dialogService, + cipher.name, + application, + ); + + const result = await firstValueFrom(dialogRef.closed); + return ipc.platform.sshAgent.signRequestResponse(requestId, result); + }), + takeUntil(this.destroy$), + ) + .subscribe(); + + this.accountService.activeAccount$.pipe(skip(1), takeUntil(this.destroy$)).subscribe({ + next: (account) => { + if (!this.isFeatureFlagEnabled) { + return; + } + + this.logService.info("Active account changed, clearing SSH keys"); + ipc.platform.sshAgent + .clearKeys() + .catch((e) => this.logService.error("Failed to clear SSH keys", e)); + }, + error: (e: unknown) => { + if (!this.isFeatureFlagEnabled) { + return; + } + + this.logService.error("Error in active account observable", e); + ipc.platform.sshAgent + .clearKeys() + .catch((e) => this.logService.error("Failed to clear SSH keys", e)); + }, + complete: () => { + if (!this.isFeatureFlagEnabled) { + return; + } + + this.logService.info("Active account observable completed, clearing SSH keys"); + ipc.platform.sshAgent + .clearKeys() + .catch((e) => this.logService.error("Failed to clear SSH keys", e)); + }, + }); + + combineLatest([ + timer(0, this.SSH_REFRESH_INTERVAL), + this.desktopSettingsService.sshAgentEnabled$, + ]) + .pipe( + concatMap(async ([, enabled]) => { + if (!this.isFeatureFlagEnabled) { + return; + } + + if (!enabled) { + await ipc.platform.sshAgent.clearKeys(); + return; + } + + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const authStatus = await firstValueFrom( + this.authService.authStatusFor$(activeAccount.id), + ); + if (authStatus !== AuthenticationStatus.Unlocked) { + return; + } + + const ciphers = await this.cipherService.getAllDecrypted(); + if (ciphers == null) { + await ipc.platform.sshAgent.lock(); + return; + } + + const sshCiphers = ciphers.filter( + (cipher) => + cipher.type === CipherType.SshKey && + !cipher.isDeleted && + cipher.organizationId === null, + ); + const keys = sshCiphers.map((cipher) => { + return { + name: cipher.name, + privateKey: cipher.sshKey.privateKey, + cipherId: cipher.id, + }; + }); + await ipc.platform.sshAgent.setKeys(keys); + }), + takeUntil(this.destroy$), + ) + .subscribe(); } ngOnDestroy() { diff --git a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts index cd4c7df66ec..2baba6275bc 100644 --- a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts +++ b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as http from "http"; import { ipcMain } from "electron"; diff --git a/apps/desktop/src/platform/services/version.service.ts b/apps/desktop/src/platform/services/version.service.ts new file mode 100644 index 00000000000..2628f83d593 --- /dev/null +++ b/apps/desktop/src/platform/services/version.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; + +@Injectable({ + providedIn: "root", +}) +export class VersionService { + constructor(private sdkService: SdkService) {} + + init() { + ipc.platform.versions.registerSdkVersionProvider(async (resolve) => { + const version = await firstValueFrom(this.sdkService.version$); + resolve(version); + }); + } +} diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 57b20490a4f..0fb2db37518 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -1,6 +1,7 @@ import { contextBridge } from "electron"; import auth from "./auth/preload"; +import autofill from "./autofill/preload"; import keyManagement from "./key-management/preload"; import platform from "./platform/preload"; @@ -17,6 +18,7 @@ import platform from "./platform/preload"; // Each team owns a subspace of the `ipc` global variable in the renderer. export const ipc = { auth, + autofill, platform, keyManagement, }; diff --git a/apps/desktop/src/scss/left-nav.scss b/apps/desktop/src/scss/left-nav.scss index 4404110ba65..d65e60079a5 100644 --- a/apps/desktop/src/scss/left-nav.scss +++ b/apps/desktop/src/scss/left-nav.scss @@ -116,6 +116,7 @@ color: themed("primaryColor"); font-weight: bold; } + max-width: 90%; } .edit-button { diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index 6f8112b1ca0..23a4644d3da 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -2,7 +2,7 @@ $dark-icon-themes: "theme_dark", "theme_nord"; -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 14px; $font-size-large: 18px; diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts new file mode 100644 index 00000000000..13b668f6b83 --- /dev/null +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -0,0 +1,123 @@ +import { NgZone } from "@angular/core"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +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 { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { FakeAccountService } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; + +import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; + +import { BiometricMessageHandlerService } from "./biometric-message-handler.service"; + +(global as any).ipc = { + platform: { + reloadProcess: jest.fn(), + }, +}; + +const SomeUser = "SomeUser" as UserId; +const AnotherUser = "SomeOtherUser" as UserId; +const accounts = { + [SomeUser]: { + name: "some user", + email: "some.user@example.com", + emailVerified: true, + }, + [AnotherUser]: { + name: "some other user", + email: "some.other.user@example.com", + emailVerified: true, + }, +}; + +describe("BiometricMessageHandlerService", () => { + let service: BiometricMessageHandlerService; + + let cryptoFunctionService: MockProxy; + let keyService: MockProxy; + let encryptService: MockProxy; + let logService: MockProxy; + let messagingService: MockProxy; + let desktopSettingsService: DesktopSettingsService; + let biometricStateService: BiometricStateService; + let biometricsService: MockProxy; + let dialogService: MockProxy; + let accountService: AccountService; + let authService: MockProxy; + let ngZone: MockProxy; + + beforeEach(() => { + cryptoFunctionService = mock(); + keyService = mock(); + encryptService = mock(); + logService = mock(); + messagingService = mock(); + desktopSettingsService = mock(); + biometricStateService = mock(); + biometricsService = mock(); + dialogService = mock(); + + accountService = new FakeAccountService(accounts); + authService = mock(); + ngZone = mock(); + + service = new BiometricMessageHandlerService( + cryptoFunctionService, + keyService, + encryptService, + logService, + messagingService, + desktopSettingsService, + biometricStateService, + biometricsService, + dialogService, + accountService, + authService, + ngZone, + ); + }); + + describe("process reload", () => { + const testCases = [ + // don't reload when the active user is the requested one and unlocked + [SomeUser, AuthenticationStatus.Unlocked, SomeUser, false, false], + // do reload when the active user is the requested one but locked + [SomeUser, AuthenticationStatus.Locked, SomeUser, false, true], + // always reload when another user is active than the requested one + [SomeUser, AuthenticationStatus.Unlocked, AnotherUser, false, true], + [SomeUser, AuthenticationStatus.Locked, AnotherUser, false, true], + + // don't reload in dev mode + [SomeUser, AuthenticationStatus.Unlocked, SomeUser, true, false], + [SomeUser, AuthenticationStatus.Locked, SomeUser, true, false], + [SomeUser, AuthenticationStatus.Unlocked, AnotherUser, true, false], + [SomeUser, AuthenticationStatus.Locked, AnotherUser, true, false], + ]; + + it.each(testCases)( + "process reload for active user %s with auth status %s and other user %s and isdev: %s should process reload: %s", + async (activeUser, authStatus, messageUser, isDev, shouldReload) => { + await accountService.switchAccount(activeUser as UserId); + authService.authStatusFor$.mockReturnValue(of(authStatus as AuthenticationStatus)); + (global as any).ipc.platform.isDev = isDev; + (global as any).ipc.platform.reloadProcess.mockClear(); + await service.processReloadWhenRequired(messageUser as UserId); + + if (shouldReload) { + expect((global as any).ipc.platform.reloadProcess).toHaveBeenCalled(); + } else { + expect((global as any).ipc.platform.reloadProcess).not.toHaveBeenCalled(); + } + }, + ); + }); +}); diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts new file mode 100644 index 00000000000..74f80785fca --- /dev/null +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -0,0 +1,372 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Injectable, NgZone } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +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 { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.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 { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { + BiometricStateService, + BiometricsCommands, + BiometricsService, + BiometricsStatus, + KeyService, +} from "@bitwarden/key-management"; + +import { BrowserSyncVerificationDialogComponent } from "../app/components/browser-sync-verification-dialog.component"; +import { LegacyMessage } from "../models/native-messaging/legacy-message"; +import { LegacyMessageWrapper } from "../models/native-messaging/legacy-message-wrapper"; +import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; + +const MessageValidTimeout = 10 * 1000; +const HashAlgorithmForAsymmetricEncryption = "sha1"; + +@Injectable() +export class BiometricMessageHandlerService { + constructor( + private cryptoFunctionService: CryptoFunctionService, + private keyService: KeyService, + private encryptService: EncryptService, + private logService: LogService, + private messagingService: MessagingService, + private desktopSettingService: DesktopSettingsService, + private biometricStateService: BiometricStateService, + private biometricsService: BiometricsService, + private dialogService: DialogService, + private accountService: AccountService, + private authService: AuthService, + private ngZone: NgZone, + ) {} + + async handleMessage(msg: LegacyMessageWrapper) { + const { appId, message: rawMessage } = msg as LegacyMessageWrapper; + + // Request to setup secure encryption + if ("command" in rawMessage && rawMessage.command === "setupEncryption") { + const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey); + + // Validate the UserId to ensure we are logged into the same account. + const accounts = await firstValueFrom(this.accountService.accounts$); + const userIds = Object.keys(accounts); + if (!userIds.includes(rawMessage.userId)) { + this.logService.info( + "[Native Messaging IPC] Received message for user that is not logged into the desktop app.", + ); + ipc.platform.nativeMessaging.sendMessage({ + command: "wrongUserId", + appId: appId, + }); + return; + } + + if (await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$)) { + this.logService.info("[Native Messaging IPC] Requesting fingerprint verification."); + ipc.platform.nativeMessaging.sendMessage({ + command: "verifyFingerprint", + appId: appId, + }); + + const fingerprint = await this.keyService.getFingerprint( + rawMessage.userId, + remotePublicKey, + ); + + this.messagingService.send("setFocus"); + + const dialogRef = this.ngZone.run(() => + BrowserSyncVerificationDialogComponent.open(this.dialogService, { fingerprint }), + ); + + const browserSyncVerified = await firstValueFrom(dialogRef.closed); + + if (browserSyncVerified !== true) { + this.logService.info("[Native Messaging IPC] Fingerprint verification failed."); + return; + } + } + + await this.secureCommunication(remotePublicKey, appId); + return; + } + + if ((await ipc.platform.ephemeralStore.getEphemeralValue(appId)) == null) { + this.logService.info( + "[Native Messaging IPC] Epheremal secret for secure channel is missing. Invalidating encryption...", + ); + ipc.platform.nativeMessaging.sendMessage({ + command: "invalidateEncryption", + appId: appId, + }); + return; + } + + const message: LegacyMessage = JSON.parse( + await this.encryptService.decryptToUtf8( + rawMessage as EncString, + SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), + ), + ); + + // Shared secret is invalidated, force re-authentication + if (message == null) { + this.logService.info( + "[Native Messaging IPC] Secure channel failed to decrypt message. Invalidating encryption...", + ); + ipc.platform.nativeMessaging.sendMessage({ + command: "invalidateEncryption", + appId: appId, + }); + return; + } + + if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { + this.logService.info("[Native Messaging IPC] Received a too old message. Ignoring."); + return; + } + + const messageId = message.messageId; + + switch (message.command) { + case BiometricsCommands.UnlockWithBiometricsForUser: { + await this.handleUnlockWithBiometricsForUser(message, messageId, appId); + break; + } + case BiometricsCommands.AuthenticateWithBiometrics: { + try { + const unlocked = await this.biometricsService.authenticateWithBiometrics(); + await this.send( + { + command: BiometricsCommands.AuthenticateWithBiometrics, + messageId, + response: unlocked, + }, + appId, + ); + } catch (e) { + this.logService.error("[Native Messaging IPC] Biometric authentication failed", e); + await this.send( + { command: BiometricsCommands.AuthenticateWithBiometrics, messageId, response: false }, + appId, + ); + } + break; + } + case BiometricsCommands.GetBiometricsStatus: { + const status = await this.biometricsService.getBiometricsStatus(); + return this.send( + { + command: BiometricsCommands.GetBiometricsStatus, + messageId, + response: status, + }, + appId, + ); + } + case BiometricsCommands.GetBiometricsStatusForUser: { + let status = await this.biometricsService.getBiometricsStatusForUser( + message.userId as UserId, + ); + if (status == BiometricsStatus.NotEnabledLocally) { + status = BiometricsStatus.NotEnabledInConnectedDesktopApp; + } + return this.send( + { + command: BiometricsCommands.GetBiometricsStatusForUser, + messageId, + response: status, + }, + appId, + ); + } + // TODO: legacy, remove after 2025.3 + case BiometricsCommands.IsAvailable: { + const available = + (await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available; + return this.send( + { + command: BiometricsCommands.IsAvailable, + response: available ? "available" : "not available", + }, + appId, + ); + } + // TODO: legacy, remove after 2025.3 + case BiometricsCommands.Unlock: { + const isTemporarilyDisabled = + (await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) && + !((await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available); + if (isTemporarilyDisabled) { + return this.send({ command: "biometricUnlock", response: "not available" }, appId); + } + + if (!((await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available)) { + return this.send({ command: "biometricUnlock", response: "not supported" }, appId); + } + + const userId = + (message.userId as UserId) ?? + (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)))); + + if (userId == null) { + return this.send({ command: "biometricUnlock", response: "not unlocked" }, appId); + } + + const biometricUnlockPromise = + message.userId == null + ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) + : this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId); + if (!(await biometricUnlockPromise)) { + await this.send({ command: "biometricUnlock", response: "not enabled" }, appId); + + return this.ngZone.run(() => + this.dialogService.openSimpleDialog({ + type: "warning", + title: { key: "biometricsNotEnabledTitle" }, + content: { key: "biometricsNotEnabledDesc" }, + cancelButtonText: null, + acceptButtonText: { key: "cancel" }, + }), + ); + } + + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); + + if (userKey != null) { + await this.send( + { + command: "biometricUnlock", + response: "unlocked", + userKeyB64: userKey.keyB64, + }, + appId, + ); + + const currentlyActiveAccountId = ( + await firstValueFrom(this.accountService.activeAccount$) + ).id; + const isCurrentlyActiveAccountUnlocked = + (await this.authService.getAuthStatus(userId)) == AuthenticationStatus.Unlocked; + + // prevent proc reloading an active account, when it is the same as the browser + if (currentlyActiveAccountId != message.userId || !isCurrentlyActiveAccountUnlocked) { + await ipc.platform.reloadProcess(); + } + } else { + await this.send({ command: "biometricUnlock", response: "canceled" }, appId); + } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + await this.send({ command: "biometricUnlock", response: "canceled" }, appId); + } + break; + } + default: + this.logService.error("NativeMessage, got unknown command: " + message.command); + break; + } + } + + private async send(message: any, appId: string) { + message.timestamp = Date.now(); + + const encrypted = await this.encryptService.encrypt( + JSON.stringify(message), + SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), + ); + + ipc.platform.nativeMessaging.sendMessage({ + appId: appId, + messageId: message.messageId, + message: encrypted, + }); + } + + private async secureCommunication(remotePublicKey: Uint8Array, appId: string) { + const secret = await this.cryptoFunctionService.randomBytes(64); + await ipc.platform.ephemeralStore.setEphemeralValue( + appId, + new SymmetricCryptoKey(secret).keyB64, + ); + + this.logService.info("[Native Messaging IPC] Setting up secure channel"); + const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt( + secret, + remotePublicKey, + HashAlgorithmForAsymmetricEncryption, + ); + ipc.platform.nativeMessaging.sendMessage({ + appId: appId, + command: "setupEncryption", + messageId: -1, // to indicate to the other side that this is a new desktop client. refactor later to use proper versioning + sharedSecret: Utils.fromBufferToB64(encryptedSecret), + }); + } + + private async handleUnlockWithBiometricsForUser( + message: LegacyMessage, + messageId: number, + appId: string, + ) { + const messageUserId = message.userId as UserId; + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(messageUserId); + if (userKey != null) { + this.logService.info("[Native Messaging IPC] Biometric unlock for user: " + messageUserId); + await this.send( + { + command: BiometricsCommands.UnlockWithBiometricsForUser, + response: true, + messageId, + userKeyB64: userKey.keyB64, + }, + appId, + ); + await this.processReloadWhenRequired(messageUserId); + } else { + await this.send( + { + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId, + response: false, + }, + appId, + ); + } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + await this.send( + { command: BiometricsCommands.UnlockWithBiometricsForUser, messageId, response: false }, + appId, + ); + } + } + + /** A process reload after a biometric unlock should happen if the userkey that was used for biometric unlock is for a different user than the + * currently active account. The userkey for the active account was in memory anyways. Further, if the desktop app is locked, a reload should occur (since the userkey was not already in memory). + */ + async processReloadWhenRequired(messageUserId: UserId) { + const currentlyActiveAccountId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const isCurrentlyActiveAccountUnlocked = + (await firstValueFrom(this.authService.authStatusFor$(currentlyActiveAccountId))) == + AuthenticationStatus.Unlocked; + + if (currentlyActiveAccountId !== messageUserId || !isCurrentlyActiveAccountUnlocked) { + if (!ipc.platform.isDev) { + ipc.platform.reloadProcess(); + } + } + } +} diff --git a/apps/desktop/src/services/desktop-lock-component.service.ts b/apps/desktop/src/services/desktop-lock-component.service.ts deleted file mode 100644 index 7402779121f..00000000000 --- a/apps/desktop/src/services/desktop-lock-component.service.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { inject } from "@angular/core"; -import { combineLatest, defer, map, Observable } from "rxjs"; - -import { - BiometricsDisableReason, - LockComponentService, - UnlockOptions, -} from "@bitwarden/auth/angular"; -import { - PinServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { DeviceType } from "@bitwarden/common/enums"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; - -export class DesktopLockComponentService implements LockComponentService { - private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); - private readonly platformUtilsService = inject(PlatformUtilsService); - private readonly biometricsService = inject(BiometricsService); - private readonly pinService = inject(PinServiceAbstraction); - private readonly vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); - private readonly keyService = inject(KeyService); - - constructor() {} - - getBiometricsError(error: any): string | null { - return null; - } - - getPreviousUrl(): string | null { - return null; - } - - async isWindowVisible(): Promise { - return ipc.platform.isWindowVisible(); - } - - getBiometricsUnlockBtnText(): string { - switch (this.platformUtilsService.getDevice()) { - case DeviceType.MacOsDesktop: - return "unlockWithTouchId"; - case DeviceType.WindowsDesktop: - return "unlockWithWindowsHello"; - case DeviceType.LinuxDesktop: - return "unlockWithPolkit"; - default: - throw new Error("Unsupported platform"); - } - } - - private async isBiometricLockSet(userId: UserId): Promise { - const biometricLockSet = await this.vaultTimeoutSettingsService.isBiometricLockSet(userId); - const hasBiometricEncryptedUserKeyStored = await this.keyService.hasUserKeyStored( - KeySuffixOptions.Biometric, - userId, - ); - const platformSupportsSecureStorage = this.platformUtilsService.supportsSecureStorage(); - - return ( - biometricLockSet && (hasBiometricEncryptedUserKeyStored || !platformSupportsSecureStorage) - ); - } - - private async isBiometricsSupportedAndReady( - userId: UserId, - ): Promise<{ supportsBiometric: boolean; biometricReady: boolean }> { - const supportsBiometric = await this.biometricsService.supportsBiometric(); - const biometricReady = await ipc.keyManagement.biometric.enabled(userId); - return { supportsBiometric, biometricReady }; - } - - getAvailableUnlockOptions$(userId: UserId): Observable { - return combineLatest([ - // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(() => this.isBiometricsSupportedAndReady(userId)), - defer(() => this.isBiometricLockSet(userId)), - this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), - defer(() => this.pinService.isPinDecryptionAvailable(userId)), - ]).pipe( - map( - ([biometricsData, isBiometricsLockSet, userDecryptionOptions, pinDecryptionAvailable]) => { - const disableReason = this.getBiometricsDisabledReason( - biometricsData.supportsBiometric, - isBiometricsLockSet, - biometricsData.biometricReady, - ); - - const unlockOpts: UnlockOptions = { - masterPassword: { - enabled: userDecryptionOptions.hasMasterPassword, - }, - pin: { - enabled: pinDecryptionAvailable, - }, - biometrics: { - enabled: - biometricsData.supportsBiometric && - isBiometricsLockSet && - biometricsData.biometricReady, - disableReason: disableReason, - }, - }; - - return unlockOpts; - }, - ), - ); - } - - private getBiometricsDisabledReason( - osSupportsBiometric: boolean, - biometricLockSet: boolean, - biometricReady: boolean, - ): BiometricsDisableReason | null { - if (!osSupportsBiometric) { - return BiometricsDisableReason.NotSupportedOnOperatingSystem; - } else if (!biometricLockSet) { - return BiometricsDisableReason.EncryptedKeysUnavailable; - } else if (!biometricReady) { - return BiometricsDisableReason.SystemBiometricsUnavailable; - } - return null; - } -} diff --git a/apps/desktop/src/services/native-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts similarity index 91% rename from apps/desktop/src/services/native-message-handler.service.ts rename to apps/desktop/src/services/duckduckgo-message-handler.service.ts index a99effce9eb..fa5c2f4d9f7 100644 --- a/apps/desktop/src/services/native-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; @@ -26,8 +28,8 @@ const HashAlgorithmForAsymmetricEncryption = "sha1"; // This service handles messages using the protocol created for the DuckDuckGo integration. @Injectable() -export class NativeMessageHandlerService { - private ddgSharedSecret: SymmetricCryptoKey; +export class DuckDuckGoMessageHandlerService { + private duckduckgoSharedSecret: SymmetricCryptoKey; constructor( private stateService: StateService, @@ -109,7 +111,7 @@ export class NativeMessageHandlerService { } const secret = await this.cryptoFunctionService.randomBytes(64); - this.ddgSharedSecret = new SymmetricCryptoKey(secret); + this.duckduckgoSharedSecret = new SymmetricCryptoKey(secret); const sharedKeyB64 = new SymmetricCryptoKey(secret).keyB64; await this.stateService.setDuckDuckGoSharedKey(sharedKeyB64); @@ -128,6 +130,8 @@ export class NativeMessageHandlerService { sharedKey: Utils.fromBufferToB64(encryptedSecret), }, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.sendResponse({ messageId: messageId, @@ -151,6 +155,8 @@ export class NativeMessageHandlerService { await this.encryptedMessageHandlerService.responseDataForCommand(decryptedCommandData); await this.sendEncryptedResponse(message, { command, payload: responseData }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // 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 @@ -166,7 +172,7 @@ export class NativeMessageHandlerService { } private async decryptPayload(message: EncryptedMessage): Promise { - if (!this.ddgSharedSecret) { + if (!this.duckduckgoSharedSecret) { const storedKey = await this.stateService.getDuckDuckGoSharedKey(); if (storedKey == null) { this.sendResponse({ @@ -178,13 +184,13 @@ export class NativeMessageHandlerService { }); return; } - this.ddgSharedSecret = SymmetricCryptoKey.fromJSON({ keyB64: storedKey }); + this.duckduckgoSharedSecret = SymmetricCryptoKey.fromJSON({ keyB64: storedKey }); } try { let decryptedResult = await this.encryptService.decryptToUtf8( message.encryptedCommand as EncString, - this.ddgSharedSecret, + this.duckduckgoSharedSecret, "ddg-shared-key", ); @@ -207,7 +213,7 @@ export class NativeMessageHandlerService { originalMessage: EncryptedMessage, response: DecryptedCommandData, ) { - if (!this.ddgSharedSecret) { + if (!this.duckduckgoSharedSecret) { this.sendResponse({ messageId: originalMessage.messageId, version: NativeMessagingVersion.Latest, @@ -219,7 +225,7 @@ export class NativeMessageHandlerService { return; } - const encryptedPayload = await this.encryptPayload(response, this.ddgSharedSecret); + const encryptedPayload = await this.encryptPayload(response, this.duckduckgoSharedSecret); this.sendResponse({ messageId: originalMessage.messageId, diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 535aef307d7..43c4b9065a7 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -176,6 +178,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } @@ -220,6 +224,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } diff --git a/apps/desktop/src/services/native-messaging.service.ts b/apps/desktop/src/services/native-messaging.service.ts index 2312bfb2f6b..11dc35f95e8 100644 --- a/apps/desktop/src/services/native-messaging.service.ts +++ b/apps/desktop/src/services/native-messaging.service.ts @@ -1,48 +1,16 @@ -import { Injectable, NgZone } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { Injectable } from "@angular/core"; -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 { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserId } from "@bitwarden/common/types/guid"; -import { DialogService } from "@bitwarden/components"; -import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; - -import { BrowserSyncVerificationDialogComponent } from "../app/components/browser-sync-verification-dialog.component"; -import { LegacyMessage } from "../models/native-messaging/legacy-message"; import { LegacyMessageWrapper } from "../models/native-messaging/legacy-message-wrapper"; import { Message } from "../models/native-messaging/message"; -import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; - -import { NativeMessageHandlerService } from "./native-message-handler.service"; -const MessageValidTimeout = 10 * 1000; -const HashAlgorithmForAsymmetricEncryption = "sha1"; +import { BiometricMessageHandlerService } from "./biometric-message-handler.service"; +import { DuckDuckGoMessageHandlerService } from "./duckduckgo-message-handler.service"; @Injectable() export class NativeMessagingService { constructor( - private cryptoFunctionService: CryptoFunctionService, - private keyService: KeyService, - private encryptService: EncryptService, - private logService: LogService, - private messagingService: MessagingService, - private desktopSettingService: DesktopSettingsService, - private biometricStateService: BiometricStateService, - private biometricsService: BiometricsService, - private nativeMessageHandler: NativeMessageHandlerService, - private dialogService: DialogService, - private accountService: AccountService, - private authService: AuthService, - private ngZone: NgZone, + private duckduckgoMessageHandler: DuckDuckGoMessageHandlerService, + private biometricMessageHandler: BiometricMessageHandlerService, ) {} init() { @@ -53,202 +21,11 @@ export class NativeMessagingService { const outerMessage = msg as Message; if (outerMessage.version) { // If there is a version, it is a using the protocol created for the DuckDuckGo integration - await this.nativeMessageHandler.handleMessage(outerMessage); - return; - } - - const { appId, message: rawMessage } = msg as LegacyMessageWrapper; - - // Request to setup secure encryption - if ("command" in rawMessage && rawMessage.command === "setupEncryption") { - const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey); - - // Validate the UserId to ensure we are logged into the same account. - const accounts = await firstValueFrom(this.accountService.accounts$); - const userIds = Object.keys(accounts); - if (!userIds.includes(rawMessage.userId)) { - ipc.platform.nativeMessaging.sendMessage({ - command: "wrongUserId", - appId: appId, - }); - return; - } - - if (await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$)) { - ipc.platform.nativeMessaging.sendMessage({ - command: "verifyFingerprint", - appId: appId, - }); - - const fingerprint = await this.keyService.getFingerprint( - rawMessage.userId, - remotePublicKey, - ); - - this.messagingService.send("setFocus"); - - const dialogRef = this.ngZone.run(() => - BrowserSyncVerificationDialogComponent.open(this.dialogService, { fingerprint }), - ); - - const browserSyncVerified = await firstValueFrom(dialogRef.closed); - - if (browserSyncVerified !== true) { - return; - } - } - - await this.secureCommunication(remotePublicKey, appId); - return; - } - - if ((await ipc.platform.ephemeralStore.getEphemeralValue(appId)) == null) { - ipc.platform.nativeMessaging.sendMessage({ - command: "invalidateEncryption", - appId: appId, - }); - return; - } - - const message: LegacyMessage = JSON.parse( - await this.encryptService.decryptToUtf8( - rawMessage as EncString, - SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), - `native-messaging-session-${appId}`, - ), - ); - - // Shared secret is invalidated, force re-authentication - if (message == null) { - ipc.platform.nativeMessaging.sendMessage({ - command: "invalidateEncryption", - appId: appId, - }); + await this.duckduckgoMessageHandler.handleMessage(outerMessage); return; - } - - if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { - this.logService.error("NativeMessage is to old, ignoring."); + } else { + await this.biometricMessageHandler.handleMessage(msg as LegacyMessageWrapper); return; } - - switch (message.command) { - case "biometricUnlock": { - const isTemporarilyDisabled = - (await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) && - !(await this.biometricsService.supportsBiometric()); - if (isTemporarilyDisabled) { - return this.send({ command: "biometricUnlock", response: "not available" }, appId); - } - - if (!(await this.biometricsService.supportsBiometric())) { - return this.send({ command: "biometricUnlock", response: "not supported" }, appId); - } - - const userId = - (message.userId as UserId) ?? - (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)))); - - if (userId == null) { - return this.send({ command: "biometricUnlock", response: "not unlocked" }, appId); - } - - const biometricUnlockPromise = - message.userId == null - ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) - : this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId); - if (!(await biometricUnlockPromise)) { - await this.send({ command: "biometricUnlock", response: "not enabled" }, appId); - - return this.ngZone.run(() => - this.dialogService.openSimpleDialog({ - type: "warning", - title: { key: "biometricsNotEnabledTitle" }, - content: { key: "biometricsNotEnabledDesc" }, - cancelButtonText: null, - acceptButtonText: { key: "cancel" }, - }), - ); - } - - try { - const userKey = await this.keyService.getUserKeyFromStorage( - KeySuffixOptions.Biometric, - message.userId, - ); - - if (userKey != null) { - await this.send( - { - command: "biometricUnlock", - response: "unlocked", - userKeyB64: userKey.keyB64, - }, - appId, - ); - - const currentlyActiveAccountId = ( - await firstValueFrom(this.accountService.activeAccount$) - ).id; - const isCurrentlyActiveAccountUnlocked = - (await this.authService.getAuthStatus(userId)) == AuthenticationStatus.Unlocked; - - // prevent proc reloading an active account, when it is the same as the browser - if (currentlyActiveAccountId != message.userId || !isCurrentlyActiveAccountUnlocked) { - await ipc.platform.reloadProcess(); - } - } else { - await this.send({ command: "biometricUnlock", response: "canceled" }, appId); - } - } catch (e) { - await this.send({ command: "biometricUnlock", response: "canceled" }, appId); - } - - break; - } - case "biometricUnlockAvailable": { - const isAvailable = await this.biometricsService.supportsBiometric(); - return this.send( - { - command: "biometricUnlockAvailable", - response: isAvailable ? "available" : "not available", - }, - appId, - ); - } - default: - this.logService.error("NativeMessage, got unknown command: " + message.command); - break; - } - } - - private async send(message: any, appId: string) { - message.timestamp = Date.now(); - - const encrypted = await this.encryptService.encrypt( - JSON.stringify(message), - SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), - ); - - ipc.platform.nativeMessaging.sendMessage({ appId: appId, message: encrypted }); - } - - private async secureCommunication(remotePublicKey: Uint8Array, appId: string) { - const secret = await this.cryptoFunctionService.randomBytes(64); - await ipc.platform.ephemeralStore.setEphemeralValue( - appId, - new SymmetricCryptoKey(secret).keyB64, - ); - - const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt( - secret, - remotePublicKey, - HashAlgorithmForAsymmetricEncryption, - ); - ipc.platform.nativeMessaging.sendMessage({ - appId: appId, - command: "setupEncryption", - sharedSecret: Utils.fromBufferToB64(encryptedSecret), - }); } } diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index 0db7b60a2df..7946280e9a6 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -1,15 +1,23 @@ export enum BiometricAction { - EnabledForUser = "enabled", - OsSupported = "osSupported", Authenticate = "authenticate", - NeedsSetup = "needsSetup", + GetStatus = "status", + + UnlockForUser = "unlockForUser", + GetStatusForUser = "statusForUser", + SetKeyForUser = "setKeyForUser", + RemoveKeyForUser = "removeKeyForUser", + + SetClientKeyHalf = "setClientKeyHalf", + Setup = "setup", - CanAutoSetup = "canAutoSetup", + + GetShouldAutoprompt = "getShouldAutoprompt", + SetShouldAutoprompt = "setShouldAutoprompt", } export type BiometricMessage = { action: BiometricAction; - keySuffix?: string; key?: string; userId?: string; + data?: any; }; diff --git a/apps/desktop/src/utils.ts b/apps/desktop/src/utils.ts index 98bdebb0cc3..c798faac36e 100644 --- a/apps/desktop/src/utils.ts +++ b/apps/desktop/src/utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export type RendererMenuItem = { label?: string; type?: "normal" | "separator" | "submenu" | "checkbox" | "radio"; @@ -70,6 +72,16 @@ export function isWindowsPortable() { return isWindows() && process.env.PORTABLE_EXECUTABLE_DIR != null; } +/** + * We block the browser integration on some unsupported platforms, which also + * blocks partially supported platforms (mac .dmg in dev builds) / prevents + * experimenting with the feature for QA. So this env var allows overriding + * the block. + */ +export function allowBrowserintegrationOverride() { + return process.env.ALLOW_BROWSER_INTEGRATION_OVERRIDE === "true"; +} + /** * Sanitize user agent so external resources used by the app can't built data on our users. */ diff --git a/apps/desktop/src/vault/app/accounts/premium.component.ts b/apps/desktop/src/vault/app/accounts/premium.component.ts index 373e5d88177..4b547384545 100644 --- a/apps/desktop/src/vault/app/accounts/premium.component.ts +++ b/apps/desktop/src/vault/app/accounts/premium.component.ts @@ -2,13 +2,13 @@ import { Component } from "@angular/core"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DialogService } from "@bitwarden/components"; @Component({ @@ -22,10 +22,10 @@ export class PremiumComponent extends BasePremiumComponent { apiService: ApiService, configService: ConfigService, logService: LogService, - stateService: StateService, dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( i18nService, @@ -36,6 +36,7 @@ export class PremiumComponent extends BasePremiumComponent { dialogService, environmentService, billingAccountProfileStateService, + accountService, ); } } 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 57b11928bef..6244f585bae 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.html +++ b/apps/desktop/src/vault/app/vault/add-edit.component.html @@ -512,16 +512,6 @@

+
+ +

+ + {{ "estimatedTax" | i18n }} + + + {{ estimatedTax | currency: "USD" : "$" }} + +

+
+

(); + protected taxInformation: TaxInformation; + constructor( @Inject(DIALOG_DATA) private dialogParams: ChangePlanDialogParams, private dialogRef: DialogRef, @@ -187,6 +207,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, + private taxService: TaxServiceAbstraction, + private organizationBillingService: OrganizationBillingService, ) {} async ngOnInit(): Promise { @@ -199,6 +221,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.sub = this.dialogParams.subscription ?? (await this.organizationApiService.getSubscription(this.dialogParams.organizationId)); + this.dialogHeaderName = this.resolveHeaderName(this.sub); this.organizationId = this.dialogParams.organizationId; this.currentPlan = this.sub?.plan; this.selectedPlan = this.sub?.plan; @@ -265,11 +288,32 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.setInitialPlanSelection(); this.loading = false; + + const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); + this.taxInformation = TaxInformation.from(taxInfo); + + this.refreshSalesTax(); + } + + resolveHeaderName(subscription: OrganizationSubscriptionResponse): string { + if (subscription.subscription != null) { + this.isSubscriptionCanceled = subscription.subscription.cancelled; + if (subscription.subscription.cancelled) { + return this.i18nService.t("restartSubscription"); + } + } + + return this.i18nService.t( + "upgradeFreeOrganization", + this.resolvePlanName(this.dialogParams.productTierType), + ); } setInitialPlanSelection() { this.focusedIndex = this.selectableProducts.length - 1; - this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + if (!this.isSubscriptionCanceled) { + this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + } } getPlanByType(productTier: ProductTierType) { @@ -348,7 +392,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { switch (cardState) { case PlanCardState.Selected: { return [ - "tw-group", "tw-cursor-pointer", "tw-block", "tw-rounded", @@ -375,6 +418,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ]; } case PlanCardState.Disabled: { + if (this.isSubscriptionCanceled) { + return [ + "tw-cursor-not-allowed", + "tw-bg-secondary-100", + "tw-font-normal", + "tw-bg-blur", + "tw-text-muted", + "tw-block", + "tw-rounded", + "tw-w-80", + ]; + } + return [ "tw-cursor-not-allowed", "tw-bg-secondary-100", @@ -396,11 +452,17 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return; } - if (plan === this.currentPlan) { + if (plan === this.currentPlan && !this.isSubscriptionCanceled) { return; } this.selectedPlan = plan; this.formGroup.patchValue({ productTier: plan.productTier }); + + try { + this.refreshSalesTax(); + } catch { + this.estimatedTax = 0; + } } ngOnDestroy() { @@ -427,6 +489,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get selectableProducts() { + if (this.isSubscriptionCanceled) { + // Return only the current plan if the subscription is canceled + return [this.currentPlan]; + } + if (this.acceptingSponsorship) { const familyPlan = this.passwordManagerPlans.find( (plan) => plan.type === PlanType.FamiliesAnnually, @@ -566,12 +633,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ); } - get taxCharges() { - return this.taxComponent != null && this.taxComponent.taxRate != null - ? (this.taxComponent.taxRate / 100) * this.passwordManagerSubtotal - : 0; - } - get passwordManagerSeats() { if (this.selectedPlan.productTier === ProductTierType.Families) { return this.selectedPlan.PasswordManager.baseSeats; @@ -583,15 +644,15 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { if (this.organization.useSecretsManager) { return ( this.passwordManagerSubtotal + - this.additionalStorageTotal(this.selectedPlan) + - this.secretsManagerSubtotal + - this.taxCharges || 0 + this.additionalStorageTotal(this.selectedPlan) + + this.secretsManagerSubtotal + + this.estimatedTax ); } return ( this.passwordManagerSubtotal + - this.additionalStorageTotal(this.selectedPlan) + - this.taxCharges || 0 + this.additionalStorageTotal(this.selectedPlan) + + this.estimatedTax ); } @@ -644,8 +705,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } changedCountry() { - if (this.deprecateStripeSourcesAPI && this.paymentV2Component && this.taxComponent) { - this.paymentV2Component.showBankAccount = this.taxComponent.country === "US"; + if (this.deprecateStripeSourcesAPI && this.paymentV2Component) { + this.paymentV2Component.showBankAccount = this.taxInformation.country === "US"; if ( !this.paymentV2Component.showBankAccount && @@ -653,8 +714,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ) { this.paymentV2Component.select(PaymentMethodType.Card); } - } else if (this.paymentComponent && this.taxComponent) { - this.paymentComponent!.hideBank = this.taxComponent?.taxFormGroup?.value.country !== "US"; + } else if (this.paymentComponent && this.taxInformation) { + this.paymentComponent!.hideBank = this.taxInformation.country !== "US"; // Bank Account payments are only available for US customers if ( this.paymentComponent.hideBank && @@ -666,19 +727,31 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } } + protected taxInformationChanged(event: TaxInformation): void { + this.taxInformation = event; + this.changedCountry(); + this.refreshSalesTax(); + } + submit = async () => { - if (!this.taxComponent?.taxFormGroup.valid && this.taxComponent?.taxFormGroup.touched) { - this.taxComponent?.taxFormGroup.markAllAsTouched(); + if (this.taxComponent !== undefined && !this.taxComponent.validate()) { return; } const doSubmit = async (): Promise => { let orgId: string = null; - orgId = await this.updateOrganization(); + if (this.isSubscriptionCanceled) { + await this.restartSubscription(); + orgId = this.organizationId; + } else { + orgId = await this.updateOrganization(); + } this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t("organizationUpgraded"), + message: this.isSubscriptionCanceled + ? this.i18nService.t("restartOrganizationSubscription") + : this.i18nService.t("organizationUpgraded"), }); await this.apiService.refreshIdentityToken(); @@ -708,6 +781,44 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.dialogRef.close(); }; + private async restartSubscription() { + const org = await this.organizationApiService.get(this.organizationId); + const organization: OrganizationInformation = { + name: org.name, + billingEmail: org.billingEmail, + }; + + const plan: PlanInformation = { + type: this.selectedPlan.type, + passwordManagerSeats: org.seats, + }; + + if (org.useSecretsManager) { + plan.subscribeToSecretsManager = true; + plan.secretsManagerSeats = org.smSeats; + } + + let paymentMethod: [string, PaymentMethodType]; + + if (this.deprecateStripeSourcesAPI) { + const { type, token } = await this.paymentV2Component.tokenize(); + paymentMethod = [token, type]; + } else { + paymentMethod = await this.paymentComponent.createPaymentToken(); + } + + const payment: PaymentInformation = { + paymentMethod, + billing: this.getBillingInformationFromTaxInfoComponent(), + }; + + await this.organizationBillingService.restartSubscription(this.organization.id, { + organization, + plan, + payment, + }); + } + private async updateOrganization() { const request = new OrganizationUpgradeRequest(); if (this.selectedPlan.productTier !== ProductTierType.Families) { @@ -722,8 +833,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; if (this.showPayment) { - request.billingAddressCountry = this.taxComponent.taxFormGroup?.value.country; - request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode; + request.billingAddressCountry = this.taxInformation.country; + request.billingAddressPostalCode = this.taxInformation.postalCode; } // Secrets Manager @@ -734,15 +845,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const tokenizedPaymentSource = await this.paymentV2Component.tokenize(); const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); updatePaymentMethodRequest.paymentSource = tokenizedPaymentSource; - updatePaymentMethodRequest.taxInformation = { - country: this.taxComponent.country, - postalCode: this.taxComponent.postalCode, - taxId: this.taxComponent.taxId, - line1: this.taxComponent.line1, - line2: this.taxComponent.line2, - city: this.taxComponent.city, - state: this.taxComponent.state, - }; + updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( + this.taxInformation, + ); await this.billingApiService.updateOrganizationPaymentMethod( this.organizationId, @@ -753,8 +858,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const paymentRequest = new PaymentRequest(); paymentRequest.paymentToken = tokenResult[0]; paymentRequest.paymentMethodType = tokenResult[1]; - paymentRequest.country = this.taxComponent.taxFormGroup?.value.country; - paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode; + paymentRequest.country = this.taxInformation.country; + paymentRequest.postalCode = this.taxInformation.postalCode; await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); } } @@ -790,6 +895,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return text; } + private getBillingInformationFromTaxInfoComponent(): BillingInformation { + return { + country: this.taxInformation.country, + postalCode: this.taxInformation.postalCode, + taxId: this.taxInformation.taxId, + addressLine1: this.taxInformation.line1, + addressLine2: this.taxInformation.line2, + city: this.taxInformation.city, + state: this.taxInformation.state, + }; + } + private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void { request.useSecretsManager = this.organization.useSecretsManager; if (!this.organization.useSecretsManager) { @@ -943,4 +1060,53 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { manageSelectableProduct(index: number) { return index; } + + private refreshSalesTax(): void { + if (!this.taxInformation.country || !this.taxInformation.postalCode) { + return; + } + + const request: PreviewOrganizationInvoiceRequest = { + organizationId: this.organizationId, + passwordManager: { + additionalStorage: 0, + plan: this.selectedPlan?.type, + seats: this.sub.seats, + }, + taxInformation: { + postalCode: this.taxInformation.postalCode, + country: this.taxInformation.country, + taxId: this.taxInformation.taxId, + }, + }; + + if (this.organization.useSecretsManager) { + request.secretsManager = { + seats: this.sub.smSeats, + additionalMachineAccounts: this.sub.smServiceAccounts, + }; + } + + this.taxService + .previewOrganizationInvoice(request) + .then((invoice) => { + this.estimatedTax = invoice.taxAmount; + }) + .catch((error) => { + this.toastService.showToast({ + title: "", + variant: "error", + message: this.i18nService.t(error.message), + }); + }); + } + + protected canUpdatePaymentInformation(): boolean { + return ( + this.upgradeRequiresPaymentMethod || + this.showPayment || + this.isPaymentSourceEmpty() || + this.isSubscriptionCanceled + ); + } } diff --git a/apps/web/src/app/billing/organizations/change-plan.component.ts b/apps/web/src/app/billing/organizations/change-plan.component.ts index 51cdbba557e..7c25413079a 100644 --- a/apps/web/src/app/billing/organizations/change-plan.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ProductTierType } from "@bitwarden/common/billing/enums"; diff --git a/apps/web/src/app/billing/organizations/download-license.component.ts b/apps/web/src/app/billing/organizations/download-license.component.ts index 6b3a93548b4..02733322807 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogConfig, DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; diff --git a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts index 30ae39d481a..d533badabf8 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { concatMap, Subject, takeUntil } from "rxjs"; diff --git a/apps/web/src/app/billing/organizations/organization-billing.module.ts b/apps/web/src/app/billing/organizations/organization-billing.module.ts index b25cda662f2..48ac613711d 100644 --- a/apps/web/src/app/billing/organizations/organization-billing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing.module.ts @@ -8,7 +8,6 @@ import { BillingSharedModule } from "../shared"; import { AdjustSubscription } from "./adjust-subscription.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; import { BillingSyncKeyComponent } from "./billing-sync-key.component"; -import { ChangePlanDialogComponent } from "./change-plan-dialog.component"; import { ChangePlanComponent } from "./change-plan.component"; import { DownloadLicenceDialogComponent } from "./download-license.component"; import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; @@ -44,7 +43,6 @@ import { SubscriptionStatusComponent } from "./subscription-status.component"; SecretsManagerSubscribeStandaloneComponent, SubscriptionHiddenComponent, SubscriptionStatusComponent, - ChangePlanDialogComponent, OrganizationPaymentMethodComponent, ], }) diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html index e1b74abea71..d37f95e3aa2 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -335,7 +335,7 @@

{{ "summary" | i18n }}

>{{ "additionalUsers" | i18n }}: {{ "users" | i18n }}: - {{ formGroup.controls["additionalSeats"].value || 0 }} × + {{ formGroup.controls.additionalSeats.value || 0 }} × {{ (selectablePlan.isAnnual ? selectablePlan.PasswordManager.seatPrice / 12 @@ -355,7 +355,7 @@

{{ "summary" | i18n }}

*ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption" > {{ "additionalStorageGb" | i18n }}: - {{ formGroup.controls["additionalStorage"].value || 0 }} × + {{ formGroup.controls.additionalStorage.value || 0 }} × {{ (selectablePlan.isAnnual ? selectablePlan.PasswordManager.additionalStoragePricePerGb / 12 @@ -388,7 +388,7 @@

{{ "summary" | i18n }}

>{{ "additionalUsers" | i18n }}: {{ "users" | i18n }}: - {{ formGroup.controls["additionalSeats"].value || 0 }} × + {{ formGroup.controls.additionalSeats.value || 0 }} × {{ selectablePlan.PasswordManager.seatPrice | currency: "$" }} {{ "monthAbbr" | i18n }} = {{ @@ -403,7 +403,7 @@

{{ "summary" | i18n }}

*ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption" > {{ "additionalStorageGb" | i18n }}: - {{ formGroup.controls["additionalStorage"].value || 0 }} × + {{ formGroup.controls.additionalStorage.value || 0 }} × {{ selectablePlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }} {{ "monthAbbr" | i18n }} = {{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "month" | i18n }} @@ -440,7 +440,12 @@

- +
{{ "passwordManagerPlanPrice" | i18n }}: {{ passwordManagerSubtotal | currency: "USD $" }} @@ -450,7 +455,7 @@


- {{ "estimatedTax" | i18n }}: {{ taxCharges | currency: "USD $" }} + {{ "estimatedTax" | i18n }}: {{ estimatedTax | currency: "USD $" }}


diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 88b5685431f..4592f8de894 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, @@ -10,7 +12,9 @@ import { import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; +import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -24,9 +28,12 @@ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/mode import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { TaxInformation } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; +import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; @@ -48,7 +55,6 @@ import { OrganizationCreateModule } from "../../admin-console/organizations/crea import { BillingSharedModule, secretsManagerSubscribeFormFactory } from "../shared"; import { PaymentV2Component } from "../shared/payment/payment-v2.component"; import { PaymentComponent } from "../shared/payment/payment.component"; -import { TaxInfoComponent } from "../shared/tax-info.component"; interface OnSuccessArgs { organizationId: string; @@ -70,13 +76,14 @@ const Allowed2020PlansForLegacyProviders = [ export class OrganizationPlansComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(PaymentV2Component) paymentV2Component: PaymentV2Component; - @ViewChild(TaxInfoComponent) taxComponent: TaxInfoComponent; + @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; - @Input() organizationId: string; + @Input() organizationId?: string; @Input() showFree = true; @Input() showCancel = false; @Input() acceptingSponsorship = false; @Input() currentPlan: PlanResponse; + selectedFile: File; @Input() @@ -91,6 +98,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private _productTier = ProductTierType.Free; + protected taxInformation: TaxInformation; + @Input() get plan(): PlanType { return this._plan; @@ -147,7 +156,10 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { billing: BillingResponse; provider: ProviderResponse; - private destroy$ = new Subject(); + protected estimatedTax: number = 0; + protected total: number = 0; + + private destroy$: Subject = new Subject(); constructor( private apiService: ApiService, @@ -166,6 +178,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private toastService: ToastService, private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, + private taxService: TaxServiceAbstraction, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -179,6 +192,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.organization = await this.organizationService.get(this.organizationId); this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); + this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); + } else { + this.taxInformation = await this.apiService.getTaxInfo(); } if (!this.selfHosted) { @@ -239,6 +255,16 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } this.loading = false; + + this.formGroup.valueChanges.pipe(debounceTime(1000), takeUntil(this.destroy$)).subscribe(() => { + this.refreshSalesTax(); + }); + + this.secretsManagerForm.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + this.refreshSalesTax(); + }); } ngOnDestroy() { @@ -436,17 +462,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return this.selectedPlan.trialPeriodDays != null; } - get taxCharges() { - return this.taxComponent != null && this.taxComponent.taxRate != null - ? (this.taxComponent.taxRate / 100) * - (this.passwordManagerSubtotal + this.secretsManagerSubtotal) - : 0; - } - - get total() { - return this.passwordManagerSubtotal + this.secretsManagerSubtotal + this.taxCharges || 0; - } - get paymentDesc() { if (this.acceptingSponsorship) { return this.i18nService.t("paymentSponsored"); @@ -552,9 +567,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.changedProduct(); } - changedCountry() { + protected changedCountry(): void { if (this.deprecateStripeSourcesAPI) { - this.paymentV2Component.showBankAccount = this.taxComponent.country === "US"; + this.paymentV2Component.showBankAccount = this.taxInformation?.country === "US"; if ( !this.paymentV2Component.showBankAccount && this.paymentV2Component.selected === PaymentMethodType.BankAccount @@ -562,7 +577,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.paymentV2Component.select(PaymentMethodType.Card); } } else { - this.paymentComponent.hideBank = this.taxComponent.taxFormGroup?.value.country !== "US"; + this.paymentComponent.hideBank = this.taxInformation?.country !== "US"; if ( this.paymentComponent.hideBank && this.paymentComponent.method === PaymentMethodType.BankAccount @@ -573,28 +588,31 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } - cancel() { + protected onTaxInformationChanged(event: TaxInformation): void { + this.taxInformation = event; + this.changedCountry(); + this.refreshSalesTax(); + } + + protected cancel(): void { this.onCanceled.emit(); } - setSelectedFile(event: Event) { + protected setSelectedFile(event: Event): void { const fileInputEl = event.target; this.selectedFile = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null; } submit = async () => { - if (this.taxComponent) { - if (!this.taxComponent?.taxFormGroup.valid) { - this.taxComponent?.taxFormGroup.markAllAsTouched(); - return; - } + if (this.taxComponent && !this.taxComponent.validate()) { + return; } if (this.singleOrgPolicyBlock) { return; } const doSubmit = async (): Promise => { - let orgId: string = null; + let orgId: string; if (this.createOrganization) { const orgKey = await this.keyService.makeOrgKey(); const key = orgKey[0].encryptedString; @@ -605,11 +623,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { const collectionCt = collection.encryptedString; const orgKeys = await this.keyService.makeKeyPair(orgKey[1]); - if (this.selfHosted) { - orgId = await this.createSelfHosted(key, collectionCt, orgKeys); - } else { - orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); - } + orgId = this.selfHosted + ? await this.createSelfHosted(key, collectionCt, orgKeys) + : await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); this.toastService.showToast({ variant: "success", @@ -617,7 +633,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { message: this.i18nService.t("organizationReadyToGo"), }); } else { - orgId = await this.updateOrganization(orgId); + orgId = await this.updateOrganization(); this.toastService.showToast({ variant: "success", title: null, @@ -651,7 +667,63 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.messagingService.send("organizationCreated", { organizationId }); }; - private async updateOrganization(orgId: string) { + protected get showTaxIdField(): boolean { + switch (this.formGroup.controls.productTier.value) { + case ProductTierType.Free: + case ProductTierType.Families: + return false; + default: + return true; + } + } + + private refreshSalesTax(): void { + if (this.formGroup.controls.plan.value == PlanType.Free) { + this.estimatedTax = 0; + return; + } + + if (!this.taxComponent.validate()) { + return; + } + + const request: PreviewOrganizationInvoiceRequest = { + organizationId: this.organizationId, + passwordManager: { + additionalStorage: this.formGroup.controls.additionalStorage.value, + plan: this.formGroup.controls.plan.value, + seats: this.formGroup.controls.additionalSeats.value, + }, + taxInformation: { + postalCode: this.taxInformation.postalCode, + country: this.taxInformation.country, + taxId: this.taxInformation.taxId, + }, + }; + + if (this.secretsManagerForm.controls.enabled.value === true) { + request.secretsManager = { + seats: this.secretsManagerForm.controls.userSeats.value, + additionalMachineAccounts: this.secretsManagerForm.controls.additionalServiceAccounts.value, + }; + } + + this.taxService + .previewOrganizationInvoice(request) + .then((invoice) => { + this.estimatedTax = invoice.taxAmount; + this.total = invoice.totalAmount; + }) + .catch((error) => { + this.toastService.showToast({ + title: "", + variant: "error", + message: this.i18nService.t(error.message), + }); + }); + } + + private async updateOrganization() { const request = new OrganizationUpgradeRequest(); request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; @@ -659,8 +731,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; - request.billingAddressCountry = this.taxComponent.taxFormGroup?.value.country; - request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode; + request.billingAddressCountry = this.taxInformation?.country; + request.billingAddressPostalCode = this.taxInformation?.postalCode; // Secrets Manager this.buildSecretsManagerRequest(request); @@ -669,10 +741,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.deprecateStripeSourcesAPI) { const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); updatePaymentMethodRequest.paymentSource = await this.paymentV2Component.tokenize(); - const expandedTaxInfoUpdateRequest = new ExpandedTaxInfoUpdateRequest(); - expandedTaxInfoUpdateRequest.country = this.taxComponent.country; - expandedTaxInfoUpdateRequest.postalCode = this.taxComponent.postalCode; - updatePaymentMethodRequest.taxInformation = expandedTaxInfoUpdateRequest; + updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( + this.taxInformation, + ); await this.billingApiService.updateOrganizationPaymentMethod( this.organizationId, updatePaymentMethodRequest, @@ -682,8 +753,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { const paymentRequest = new PaymentRequest(); paymentRequest.paymentToken = paymentToken; paymentRequest.paymentMethodType = paymentMethodType; - paymentRequest.country = this.taxComponent.taxFormGroup?.value.country; - paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode; + paymentRequest.country = this.taxInformation?.country; + paymentRequest.postalCode = this.taxInformation?.postalCode; await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); } } @@ -707,7 +778,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { collectionCt: string, orgKeys: [string, EncString], orgKey: SymmetricCryptoKey, - ) { + ): Promise { const request = new OrganizationCreateRequest(); request.key = key; request.collectionName = collectionCt; @@ -736,15 +807,13 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; - request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode; - request.billingAddressCountry = this.taxComponent.taxFormGroup?.value.country; - if (this.taxComponent.taxFormGroup?.value.includeTaxId) { - request.taxIdNumber = this.taxComponent.taxFormGroup?.value.taxId; - request.billingAddressLine1 = this.taxComponent.taxFormGroup?.value.line1; - request.billingAddressLine2 = this.taxComponent.taxFormGroup?.value.line2; - request.billingAddressCity = this.taxComponent.taxFormGroup?.value.city; - request.billingAddressState = this.taxComponent.taxFormGroup?.value.state; - } + request.billingAddressPostalCode = this.taxInformation?.postalCode; + request.billingAddressCountry = this.taxInformation?.country; + request.taxIdNumber = this.taxInformation?.taxId; + request.billingAddressLine1 = this.taxInformation?.line1; + request.billingAddressLine2 = this.taxInformation?.line2; + request.billingAddressCity = this.taxInformation?.city; + request.billingAddressState = this.taxInformation?.state; } // Secrets Manager diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index d4d11d91e01..0805e92ee2a 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -1,6 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs"; +import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; @@ -40,6 +42,9 @@ import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.comp templateUrl: "organization-subscription-cloud.component.html", }) export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy { + static readonly QUERY_PARAM_UPGRADE: string = "upgrade"; + static readonly ROUTE_PARAM_ORGANIZATION_ID: string = "organizationId"; + sub: OrganizationSubscriptionResponse; lineItems: BillingSubscriptionItemResponse[] = []; organizationId: string; @@ -60,14 +65,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy protected readonly subscriptionHiddenIcon = SubscriptionHiddenIcon; protected readonly teamsStarter = ProductTierType.TeamsStarter; - protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$( - FeatureFlag.EnableConsolidatedBilling, - ); - - protected enableUpgradePasswordManagerSub$ = this.configService.getFeatureFlag$( - FeatureFlag.EnableUpgradePasswordManagerSub, - ); - protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( FeatureFlag.AC2476_DeprecateStripeSourcesAPI, ); @@ -88,7 +85,19 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ) {} async ngOnInit() { - if (this.route.snapshot.queryParamMap.get("upgrade")) { + this.organizationId = + this.route.snapshot.params[ + OrganizationSubscriptionCloudComponent.ROUTE_PARAM_ORGANIZATION_ID + ]; + await this.load(); + + this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$( + FeatureFlag.AC1795_UpdatedSubscriptionStatusSection, + ); + + if ( + this.route.snapshot.queryParams[OrganizationSubscriptionCloudComponent.QUERY_PARAM_UPGRADE] + ) { await this.changePlan(); const productTierTypeStr = this.route.snapshot.queryParamMap.get("productTierType"); if (productTierTypeStr != null) { @@ -98,20 +107,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } } } - - this.route.params - .pipe( - concatMap(async (params) => { - this.organizationId = params.organizationId; - await this.load(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$( - FeatureFlag.AC1795_UpdatedSubscriptionStatusSection, - ); } ngOnDestroy() { @@ -124,8 +119,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.locale = await firstValueFrom(this.i18nService.locale$); this.userOrg = await this.organizationService.get(this.organizationId); - const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$); - const isIndependentOrganizationOwner = !this.userOrg.hasProvider && this.userOrg.isOwner; const isResoldOrganizationOwner = this.userOrg.hasReseller && this.userOrg.isOwner; const isMSPUser = this.userOrg.hasProvider && this.userOrg.isProviderUser; @@ -135,7 +128,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ); this.organizationIsManagedByConsolidatedBillingMSP = - consolidatedBillingEnabled && this.userOrg.hasProvider && metadata.isManaged; + this.userOrg.hasProvider && metadata.isManaged; this.showSubscription = isIndependentOrganizationOwner || @@ -366,27 +359,20 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy }; async changePlan() { - const EnableUpgradePasswordManagerSub = await firstValueFrom( - this.enableUpgradePasswordManagerSub$, - ); - if (EnableUpgradePasswordManagerSub) { - const reference = openChangePlanDialog(this.dialogService, { - data: { - organizationId: this.organizationId, - subscription: this.sub, - productTierType: this.userOrg.productTierType, - }, - }); + const reference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: this.organizationId, + subscription: this.sub, + productTierType: this.userOrg.productTierType, + }, + }); - const result = await lastValueFrom(reference.closed); + const result = await lastValueFrom(reference.closed); - if (result === ChangePlanDialogResultType.Closed) { - return; - } - await this.load(); - } else { - this.showChangePlan = !this.showChangePlan; + if (result === ChangePlanDialogResultType.Closed) { + return; } + await this.load(); } isSecretsManagerTrial(): boolean { diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html index 5a1ccc0768a..acbb8863c70 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html @@ -65,7 +65,7 @@ {{ "launchCloudSubscription" | i18n }} - +

{{ "licenseAndBillingManagement" | i18n }}

@@ -73,7 +73,6 @@

id="automatic-sync" [value]="licenseOptions.SYNC" [disabled]="disableLicenseSyncControl" - class="tw-block" *ngIf="showAutomaticSyncAndManualUpload" > - - +
+ + +
{{ "manualUpload" | i18n }} @@ -128,7 +129,7 @@

{{ "licenseAndBillingManagementDesc" | i18n }} -

+

{{ "uploadLicense" | i18n }}

- {{ "routeToPaymentMethodTrigger" | i18n }} + {{ "clickHereToAddPaymentMethod" | i18n }} @@ -63,20 +63,5 @@

{{ "paymentMethod" | i18n }}

{{ "paymentChargedWithUnpaidSubscription" | i18n }}

- - -

{{ "taxInformation" | i18n }}

-

{{ "taxInformationDesc" | i18n }}

- - -
diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index e2178e7c02c..270ba54f70d 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -1,5 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Location } from "@angular/common"; -import { Component, OnDestroy, ViewChild } from "@angular/core"; +import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { from, lastValueFrom, switchMap } from "rxjs"; @@ -9,7 +11,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; @@ -20,7 +21,6 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { FreeTrial } from "../../../core/types/free-trial"; import { TrialFlowService } from "../../services/trial-flow.service"; -import { TaxInfoComponent } from "../../shared"; import { AddCreditDialogResult, openAddCreditDialog, @@ -34,8 +34,6 @@ import { templateUrl: "./organization-payment-method.component.html", }) export class OrganizationPaymentMethodComponent implements OnDestroy { - @ViewChild(TaxInfoComponent) taxInfoComponent: TaxInfoComponent; - organizationId: string; isUnpaid = false; accountCredit: number; @@ -153,6 +151,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { data: { initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, + productTier: this.organization?.productTierType, }, }); @@ -168,6 +167,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { data: { initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, + productTier: this.organization?.productTierType, }, }); const result = await lastValueFrom(dialogRef.closed); @@ -181,32 +181,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { } }; - protected updateTaxInformation = async (): Promise => { - this.taxInfoComponent.taxFormGroup.updateValueAndValidity(); - this.taxInfoComponent.taxFormGroup.markAllAsTouched(); - - if (this.taxInfoComponent.taxFormGroup.invalid) { - return; - } - - const request = new ExpandedTaxInfoUpdateRequest(); - request.country = this.taxInfoComponent.country; - request.postalCode = this.taxInfoComponent.postalCode; - request.taxId = this.taxInfoComponent.taxId; - request.line1 = this.taxInfoComponent.line1; - request.line2 = this.taxInfoComponent.line2; - request.city = this.taxInfoComponent.city; - request.state = this.taxInfoComponent.state; - - await this.billingApiService.updateOrganizationTaxInformation(this.organizationId, request); - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("taxInfoUpdated"), - }); - }; - protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise => { await this.billingApiService.verifyOrganizationBankAccount(this.organizationId, request); this.toastService.showToast({ diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index bc8694a5058..fc7a188f967 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -1,8 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -102,6 +106,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private toastService: ToastService, + private internalOrganizationService: InternalOrganizationServiceAbstraction, ) {} ngOnInit() { @@ -155,11 +160,20 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest ? this.formGroup.value.maxAutoscaleServiceAccounts : null; - await this.organizationApiService.updateSecretsManagerSubscription( + const response = await this.organizationApiService.updateSecretsManagerSubscription( this.organizationId, request, ); + const organization = await this.internalOrganizationService.get(this.organizationId); + + const organizationData = new OrganizationData(response, { + isMember: organization.isMember, + isProviderUser: organization.isProviderUser, + }); + + await this.internalOrganizationService.upsert(organizationData); + this.toastService.showToast({ variant: "success", title: null, diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index aae799d8089..7ad0895809c 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { FormBuilder } from "@angular/forms"; diff --git a/apps/web/src/app/billing/organizations/subscription-hidden.component.ts b/apps/web/src/app/billing/organizations/subscription-hidden.component.ts index a603fff7804..894db727b01 100644 --- a/apps/web/src/app/billing/organizations/subscription-hidden.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-hidden.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { svgIcon } from "@bitwarden/components"; diff --git a/apps/web/src/app/billing/organizations/subscription-status.component.ts b/apps/web/src/app/billing/organizations/subscription-status.component.ts index 9a0b634edcc..a097bf674e2 100644 --- a/apps/web/src/app/billing/organizations/subscription-status.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-status.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; diff --git a/apps/web/src/app/billing/services/braintree.service.ts b/apps/web/src/app/billing/services/braintree.service.ts index 04b2b7dd442..77617b5868b 100644 --- a/apps/web/src/app/billing/services/braintree.service.ts +++ b/apps/web/src/app/billing/services/braintree.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/web/src/app/billing/services/free-families-policy.service.ts b/apps/web/src/app/billing/services/free-families-policy.service.ts new file mode 100644 index 00000000000..cc53e0a32bc --- /dev/null +++ b/apps/web/src/app/billing/services/free-families-policy.service.ts @@ -0,0 +1,125 @@ +import { Injectable } from "@angular/core"; +import { combineLatest, filter, from, map, Observable, of, switchMap } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +interface EnterpriseOrgStatus { + isFreeFamilyPolicyEnabled: boolean; + belongToOneEnterpriseOrgs: boolean; + belongToMultipleEnterpriseOrgs: boolean; +} + +@Injectable({ providedIn: "root" }) +export class FreeFamiliesPolicyService { + protected enterpriseOrgStatus: EnterpriseOrgStatus = { + isFreeFamilyPolicyEnabled: false, + belongToOneEnterpriseOrgs: false, + belongToMultipleEnterpriseOrgs: false, + }; + + constructor( + private policyService: PolicyService, + private organizationService: OrganizationService, + private configService: ConfigService, + ) {} + + get showFreeFamilies$(): Observable { + return this.isFreeFamilyFlagEnabled$.pipe( + switchMap((isFreeFamilyFlagEnabled) => + isFreeFamilyFlagEnabled + ? this.getFreeFamiliesVisibility$() + : this.organizationService.canManageSponsorships$, + ), + ); + } + + private getFreeFamiliesVisibility$(): Observable { + return combineLatest([ + this.checkEnterpriseOrganizationsAndFetchPolicy(), + this.organizationService.canManageSponsorships$, + ]).pipe( + map(([orgStatus, canManageSponsorships]) => + this.shouldShowFreeFamilyLink(orgStatus, canManageSponsorships), + ), + ); + } + + private shouldShowFreeFamilyLink( + orgStatus: EnterpriseOrgStatus | null, + canManageSponsorships: boolean, + ): boolean { + if (!orgStatus) { + return false; + } + const { belongToOneEnterpriseOrgs, isFreeFamilyPolicyEnabled } = orgStatus; + return canManageSponsorships && !(belongToOneEnterpriseOrgs && isFreeFamilyPolicyEnabled); + } + + checkEnterpriseOrganizationsAndFetchPolicy(): Observable { + return this.organizationService.organizations$.pipe( + filter((organizations) => Array.isArray(organizations) && organizations.length > 0), + switchMap((organizations) => this.fetchEnterpriseOrganizationPolicy(organizations)), + ); + } + + private fetchEnterpriseOrganizationPolicy( + organizations: Organization[], + ): Observable { + const { belongToOneEnterpriseOrgs, belongToMultipleEnterpriseOrgs } = + this.evaluateEnterpriseOrganizations(organizations); + + if (!belongToOneEnterpriseOrgs) { + return of({ + isFreeFamilyPolicyEnabled: false, + belongToOneEnterpriseOrgs, + belongToMultipleEnterpriseOrgs, + }); + } + + const organizationId = this.getOrganizationIdForOneEnterprise(organizations); + if (!organizationId) { + return of({ + isFreeFamilyPolicyEnabled: false, + belongToOneEnterpriseOrgs, + belongToMultipleEnterpriseOrgs, + }); + } + + return this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy).pipe( + map((policies) => ({ + isFreeFamilyPolicyEnabled: policies.some( + (policy) => policy.organizationId === organizationId && policy.enabled, + ), + belongToOneEnterpriseOrgs, + belongToMultipleEnterpriseOrgs, + })), + ); + } + + private evaluateEnterpriseOrganizations(organizations: any[]): { + belongToOneEnterpriseOrgs: boolean; + belongToMultipleEnterpriseOrgs: boolean; + } { + const enterpriseOrganizations = organizations.filter((org) => org.canManageSponsorships); + const count = enterpriseOrganizations.length; + + return { + belongToOneEnterpriseOrgs: count === 1, + belongToMultipleEnterpriseOrgs: count > 1, + }; + } + + private getOrganizationIdForOneEnterprise(organizations: any[]): string | null { + const enterpriseOrganizations = organizations.filter((org) => org.canManageSponsorships); + return enterpriseOrganizations.length === 1 ? enterpriseOrganizations[0].id : null; + } + + private get isFreeFamilyFlagEnabled$(): Observable { + return from(this.configService.getFeatureFlag(FeatureFlag.DisableFreeFamiliesSponsorship)); + } +} diff --git a/apps/web/src/app/billing/services/reseller-warning.service.ts b/apps/web/src/app/billing/services/reseller-warning.service.ts new file mode 100644 index 00000000000..2c59ebafe05 --- /dev/null +++ b/apps/web/src/app/billing/services/reseller-warning.service.ts @@ -0,0 +1,142 @@ +import { Injectable } from "@angular/core"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +export interface ResellerWarning { + type: "info" | "warning"; + message: string; +} + +@Injectable({ providedIn: "root" }) +export class ResellerWarningService { + private readonly RENEWAL_WARNING_DAYS = 14; + private readonly GRACE_PERIOD_DAYS = 30; + + constructor(private i18nService: I18nService) {} + + getWarning( + organization: Organization, + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): ResellerWarning | null { + if (!organization.hasReseller) { + return null; // If no reseller, return null immediately + } + + // Check for past due warning first (highest priority) + if (this.shouldShowPastDueWarning(organizationBillingMetadata)) { + const gracePeriodEnd = this.getGracePeriodEndDate(organizationBillingMetadata.invoiceDueDate); + if (!gracePeriodEnd) { + return null; + } + return { + type: "warning", + message: this.i18nService.t( + "resellerPastDueWarningMsg", + organization.providerName, + this.formatDate(gracePeriodEnd), + ), + } as ResellerWarning; + } + + // Check for open invoice warning + if (this.shouldShowInvoiceWarning(organizationBillingMetadata)) { + const invoiceCreatedDate = organizationBillingMetadata.invoiceCreatedDate; + const invoiceDueDate = organizationBillingMetadata.invoiceDueDate; + if (!invoiceCreatedDate || !invoiceDueDate) { + return null; + } + return { + type: "info", + message: this.i18nService.t( + "resellerOpenInvoiceWarningMgs", + organization.providerName, + this.formatDate(organizationBillingMetadata.invoiceCreatedDate), + this.formatDate(organizationBillingMetadata.invoiceDueDate), + ), + } as ResellerWarning; + } + + // Check for renewal warning + if (this.shouldShowRenewalWarning(organizationBillingMetadata)) { + const subPeriodEndDate = organizationBillingMetadata.subPeriodEndDate; + if (!subPeriodEndDate) { + return null; + } + + return { + type: "info", + message: this.i18nService.t( + "resellerRenewalWarningMsg", + organization.providerName, + this.formatDate(organizationBillingMetadata.subPeriodEndDate), + ), + } as ResellerWarning; + } + + return null; + } + + private shouldShowRenewalWarning( + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): boolean { + if ( + !organizationBillingMetadata.hasSubscription || + !organizationBillingMetadata.subPeriodEndDate + ) { + return false; + } + const renewalDate = new Date(organizationBillingMetadata.subPeriodEndDate); + const daysUntilRenewal = Math.ceil( + (renewalDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24), + ); + return daysUntilRenewal <= this.RENEWAL_WARNING_DAYS; + } + + private shouldShowInvoiceWarning( + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): boolean { + if ( + !organizationBillingMetadata.hasOpenInvoice || + !organizationBillingMetadata.invoiceDueDate + ) { + return false; + } + const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate); + return invoiceDueDate > new Date(); + } + + private shouldShowPastDueWarning( + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): boolean { + if ( + !organizationBillingMetadata.hasOpenInvoice || + !organizationBillingMetadata.invoiceDueDate + ) { + return false; + } + const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate); + return invoiceDueDate <= new Date() && !organizationBillingMetadata.isSubscriptionUnpaid; + } + + private getGracePeriodEndDate(dueDate: Date | null): Date | null { + if (!dueDate) { + return null; + } + const gracePeriodEnd = new Date(dueDate); + gracePeriodEnd.setDate(gracePeriodEnd.getDate() + this.GRACE_PERIOD_DAYS); + return gracePeriodEnd; + } + + private formatDate(date: Date | null): string { + if (!date) { + return "N/A"; + } + return new Date(date).toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + }); + } +} diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts index d19a7ef9da8..61bc0b6cdd2 100644 --- a/apps/web/src/app/billing/services/stripe.service.ts +++ b/apps/web/src/app/billing/services/stripe.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { BankAccount } from "@bitwarden/common/billing/models/domain"; @@ -157,9 +159,9 @@ export class StripeService { base: { color: null, fontFamily: - '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + + '"DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', - fontSize: "14px", + fontSize: "16px", fontSmoothing: "antialiased", "::placeholder": { color: null, diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts index 3135a811665..a3a4ba6bba1 100644 --- a/apps/web/src/app/billing/services/trial-flow.service.ts +++ b/apps/web/src/app/billing/services/trial-flow.service.ts @@ -1,24 +1,38 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Router } from "@angular/router"; +import { lastValueFrom } from "rxjs"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; import { FreeTrial } from "../../core/types/free-trial"; +import { + ChangePlanDialogResultType, + openChangePlanDialog, +} from "../organizations/change-plan-dialog.component"; @Injectable({ providedIn: "root" }) export class TrialFlowService { + private resellerManagedOrgAlert: boolean; + constructor( private i18nService: I18nService, protected dialogService: DialogService, private router: Router, protected billingApiService: BillingApiServiceAbstraction, + private organizationApiService: OrganizationApiServiceAbstraction, + private configService: ConfigService, ) {} checkForOrgsWithUpcomingPaymentIssues( organization: Organization, @@ -52,11 +66,11 @@ export class TrialFlowService { getFreeTrialMessage(trialRemainingDays: number): string { if (trialRemainingDays >= 2) { - return this.i18nService.t("freeTrialEndPrompt", trialRemainingDays); + return this.i18nService.t("freeTrialEndPromptCount", trialRemainingDays); } else if (trialRemainingDays === 1) { - return this.i18nService.t("freeTrialEndPromptForOneDayNoOrgName"); + return this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName"); } else { - return this.i18nService.t("freeTrialEndingSoonWithoutOrgName"); + return this.i18nService.t("freeTrialEndingTodayWithoutOrgName"); } } @@ -64,16 +78,31 @@ export class TrialFlowService { org: Organization, organizationBillingMetadata: OrganizationBillingMetadataResponse, ): Promise { - if (organizationBillingMetadata.isSubscriptionUnpaid) { - const confirmed = await this.promptForPaymentNavigation(org); + if ( + organizationBillingMetadata.isSubscriptionUnpaid || + organizationBillingMetadata.isSubscriptionCanceled + ) { + const confirmed = await this.promptForPaymentNavigation( + org, + organizationBillingMetadata.isSubscriptionCanceled, + organizationBillingMetadata.isSubscriptionUnpaid, + ); if (confirmed) { await this.navigateToPaymentMethod(org?.id); } } } - private async promptForPaymentNavigation(org: Organization): Promise { - if (!org?.isOwner) { + private async promptForPaymentNavigation( + org: Organization, + isCanceled: boolean, + isUnpaid: boolean, + ): Promise { + this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( + FeatureFlag.ResellerManagedOrgAlert, + ); + + if (!org?.isOwner && !org.providerId) { await this.dialogService.openSimpleDialog({ title: this.i18nService.t("suspendedOrganizationTitle", org?.name), content: { key: "suspendedUserOrgMessage" }, @@ -83,13 +112,31 @@ export class TrialFlowService { }); return false; } - return await this.dialogService.openSimpleDialog({ - title: this.i18nService.t("suspendedOrganizationTitle", org?.name), - content: { key: "suspendedOwnerOrgMessage" }, - type: "danger", - acceptButtonText: this.i18nService.t("continue"), - cancelButtonText: this.i18nService.t("close"), - }); + + if (org.providerId && this.resellerManagedOrgAlert) { + await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", org.name), + content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] }, + type: "danger", + acceptButtonText: this.i18nService.t("close"), + cancelButtonText: null, + }); + return false; + } + + if (org.isOwner && isUnpaid) { + return await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", org.name), + content: { key: "suspendedOwnerOrgMessage" }, + type: "danger", + acceptButtonText: this.i18nService.t("continue"), + cancelButtonText: this.i18nService.t("close"), + }); + } + + if (org.isOwner && isCanceled && this.resellerManagedOrgAlert) { + await this.changePlan(org); + } } private async navigateToPaymentMethod(orgId: string) { @@ -97,4 +144,20 @@ export class TrialFlowService { state: { launchPaymentModalAutomatically: true }, }); } + + private async changePlan(org: Organization) { + const subscription = await this.organizationApiService.getSubscription(org.id); + const reference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: org.id, + subscription: subscription, + productTierType: org.productTierType, + }, + }); + + const result = await lastValueFrom(reference.closed); + if (result === ChangePlanDialogResultType.Closed) { + return; + } + } } diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index c098b6044c8..5e26e80a30a 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, @@ -8,18 +10,25 @@ import { AsyncValidatorFn, ValidationErrors, } from "@angular/forms"; -import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; +import { Router } from "@angular/router"; +import { combineLatest, 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"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { PlanSponsorshipType } from "@bitwarden/common/billing/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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; +import { FreeFamiliesPolicyService } from "../services/free-families-policy.service"; + interface RequestSponsorshipForm { selectedSponsorshipOrgId: FormControl; sponsorshipEmail: FormControl; @@ -31,6 +40,7 @@ interface RequestSponsorshipForm { }) export class SponsoredFamiliesComponent implements OnInit, OnDestroy { loading = false; + isFreeFamilyFlagEnabled: boolean; availableSponsorshipOrgs$: Observable; activeSponsorshipOrgs$: Observable; @@ -53,6 +63,10 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private accountService: AccountService, private toastService: ToastService, + private configService: ConfigService, + private policyService: PolicyService, + private freeFamiliesPolicyService: FreeFamiliesPolicyService, + private router: Router, ) { this.sponsorshipForm = this.formBuilder.group({ selectedSponsorshipOrgId: new FormControl("", { @@ -72,10 +86,36 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable)), + this.isFreeFamilyFlagEnabled = await this.configService.getFeatureFlag( + FeatureFlag.DisableFreeFamiliesSponsorship, ); + if (this.isFreeFamilyFlagEnabled) { + await this.preventAccessToFreeFamiliesPage(); + + this.availableSponsorshipOrgs$ = combineLatest([ + this.organizationService.organizations$, + this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy), + ]).pipe( + map(([organizations, policies]) => + organizations + .filter((org) => org.familySponsorshipAvailable) + .map((org) => ({ + organization: org, + isPolicyEnabled: policies.some( + (policy) => policy.organizationId === org.id && policy.enabled, + ), + })) + .filter(({ isPolicyEnabled }) => !isPolicyEnabled) + .map(({ organization }) => organization), + ), + ); + } else { + this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe( + map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable)), + ); + } + this.availableSponsorshipOrgs$.pipe(takeUntil(this._destroy)).subscribe((orgs) => { if (orgs.length === 1) { this.sponsorshipForm.patchValue({ @@ -109,6 +149,17 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { this._destroy.complete(); } + private async preventAccessToFreeFamiliesPage() { + const showFreeFamiliesPage = await firstValueFrom( + this.freeFamiliesPolicyService.showFreeFamilies$, + ); + + if (!showFreeFamiliesPage) { + await this.router.navigate(["/"]); + return; + } + } + submit = async () => { this.formPromise = this.apiService.postCreateSponsorship( this.sponsorshipForm.value.selectedSponsorshipOrgId, diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.html b/apps/web/src/app/billing/settings/sponsoring-org-row.component.html index b07cbbfad12..eeeaa256049 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.html +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.html @@ -18,7 +18,11 @@ - - diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index 98e6efcd8bd..149b4adf520 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -1,5 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Location } from "@angular/common"; -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { lastValueFrom } from "rxjs"; @@ -14,7 +16,6 @@ import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/mode import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -27,15 +28,12 @@ import { AdjustPaymentDialogResult, openAdjustPaymentDialog, } from "./adjust-payment-dialog/adjust-payment-dialog.component"; -import { TaxInfoComponent } from "./tax-info.component"; @Component({ templateUrl: "payment-method.component.html", }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil export class PaymentMethodComponent implements OnInit, OnDestroy { - @ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent; - loading = false; firstLoaded = false; billing: BillingPaymentResponse; @@ -59,7 +57,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { ]), }); - taxForm = this.formBuilder.group({}); launchPaymentModalAutomatically = false; protected freeTrialData: FreeTrial; @@ -70,7 +67,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { protected platformUtilsService: PlatformUtilsService, private router: Router, private location: Location, - private logService: LogService, private route: ActivatedRoute, private formBuilder: FormBuilder, private dialogService: DialogService, @@ -196,15 +192,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { await this.load(); }; - submitTaxInfo = async () => { - await this.taxInfo.submitTaxInfo(); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("taxInfoUpdated"), - }); - }; - determineOrgsWithUpcomingPaymentIssues() { this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( this.organization, @@ -229,10 +216,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { return this.organizationId != null; } - get headerClass() { - return this.forOrganization ? ["page-header"] : ["tabbed-header"]; - } - get paymentSourceClasses() { if (this.paymentSource == null) { return []; diff --git a/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts b/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts index 4e671ed593c..f4d0f097766 100644 --- a/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { booleanAttribute, Component, Input, OnInit } from "@angular/core"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; diff --git a/apps/web/src/app/billing/shared/payment/payment-v2.component.html b/apps/web/src/app/billing/shared/payment/payment-v2.component.html index 51fdb1738f5..9804e6bc86f 100644 --- a/apps/web/src/app/billing/shared/payment/payment-v2.component.html +++ b/apps/web/src/app/billing/shared/payment/payment-v2.component.html @@ -80,9 +80,9 @@ - - {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} - + + {{ "verifyBankAccountWithStatementDescriptorWarning" | i18n }} +
{{ "routingNumber" | i18n }} diff --git a/apps/web/src/app/billing/shared/payment/payment-v2.component.ts b/apps/web/src/app/billing/shared/payment/payment-v2.component.ts index c5ce4eac400..10cf7ccb702 100644 --- a/apps/web/src/app/billing/shared/payment/payment-v2.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Subject } from "rxjs"; diff --git a/apps/web/src/app/billing/shared/payment/payment.component.ts b/apps/web/src/app/billing/shared/payment/payment.component.ts index ab81a602d23..e067a5ee490 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; @@ -84,9 +86,9 @@ export class PaymentComponent implements OnInit, OnDestroy { base: { color: null, fontFamily: - '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + + '"DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', - fontSize: "14px", + fontSize: "16px", fontSmoothing: "antialiased", "::placeholder": { color: null, diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/abstract-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/abstract-self-hosting-license-uploader.component.ts index 01605eae7dc..c1ceb71cf9e 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/abstract-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/abstract-self-hosting-license-uploader.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts index 87edce7b890..41cc977d46f 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Output } from "@angular/core"; import { FormBuilder } from "@angular/forms"; diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts index 7bb3e3cada0..23041ccb1ae 100644 --- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts +++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { Subject, startWith, takeUntil } from "rxjs"; diff --git a/apps/web/src/app/billing/shared/tax-info.component.html b/apps/web/src/app/billing/shared/tax-info.component.html index 82d5104a53a..4a42c0c1109 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.html +++ b/apps/web/src/app/billing/shared/tax-info.component.html @@ -13,51 +13,41 @@
-
+
{{ "zipPostalCode" | i18n }}
-
- - - {{ "includeVAT" | i18n }} - -
-
-
-
- - {{ "taxIdNumber" | i18n }} - - -
-
-
-
+
{{ "address1" | i18n }}
-
+
{{ "address2" | i18n }}
-
+
{{ "cityTown" | i18n }}
-
+
{{ "stateProvince" | i18n }}
+
+ + {{ "taxIdNumber" | i18n }} + + +
diff --git a/apps/web/src/app/billing/shared/tax-info.component.ts b/apps/web/src/app/billing/shared/tax-info.component.ts index 2cd8f7dc366..214364e4cf2 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.ts +++ b/apps/web/src/app/billing/shared/tax-info.component.ts @@ -1,29 +1,20 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { CountryListItem } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { TaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/tax-info-update.request"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; -import { TaxRateResponse } from "@bitwarden/common/billing/models/response/tax-rate.response"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SharedModule } from "../../shared"; -type TaxInfoView = Omit & { - includeTaxId: boolean; - [key: string]: unknown; -}; - -type CountryList = { - name: string; - value: string; - disabled: boolean; -}; - @Component({ selector: "app-tax-info", templateUrl: "tax-info.component.html", @@ -31,359 +22,68 @@ type CountryList = { imports: [SharedModule], }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class TaxInfoComponent implements OnInit { - @Input() trialFlow = false; - @Output() onCountryChanged = new EventEmitter(); +export class TaxInfoComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); + @Input() trialFlow = false; + @Output() countryChanged = new EventEmitter(); + @Output() taxInformationChanged: EventEmitter = new EventEmitter(); + taxFormGroup = new FormGroup({ - country: new FormControl(null, [Validators.required]), - postalCode: new FormControl(null), - includeTaxId: new FormControl(null), - taxId: new FormControl(null), - line1: new FormControl(null), - line2: new FormControl(null), - city: new FormControl(null), - state: new FormControl(null), + country: new FormControl(null, [Validators.required]), + postalCode: new FormControl(null, [Validators.required]), + taxId: new FormControl(null), + line1: new FormControl(null), + line2: new FormControl(null), + city: new FormControl(null), + state: new FormControl(null), }); + protected isTaxSupported: boolean; + loading = true; organizationId: string; providerId: string; - taxInfo: TaxInfoView = { - taxId: null, - line1: null, - line2: null, - city: null, - state: null, - postalCode: null, - country: "US", - includeTaxId: false, - }; - countryList: CountryList[] = [ - { name: "-- Select --", value: "", disabled: false }, - { name: "United States", value: "US", disabled: false }, - { name: "China", value: "CN", disabled: false }, - { name: "France", value: "FR", disabled: false }, - { name: "Germany", value: "DE", disabled: false }, - { name: "Canada", value: "CA", disabled: false }, - { name: "United Kingdom", value: "GB", disabled: false }, - { name: "Australia", value: "AU", disabled: false }, - { name: "India", value: "IN", disabled: false }, - { name: "", value: "-", disabled: true }, - { name: "Afghanistan", value: "AF", disabled: false }, - { name: "Åland Islands", value: "AX", disabled: false }, - { name: "Albania", value: "AL", disabled: false }, - { name: "Algeria", value: "DZ", disabled: false }, - { name: "American Samoa", value: "AS", disabled: false }, - { name: "Andorra", value: "AD", disabled: false }, - { name: "Angola", value: "AO", disabled: false }, - { name: "Anguilla", value: "AI", disabled: false }, - { name: "Antarctica", value: "AQ", disabled: false }, - { name: "Antigua and Barbuda", value: "AG", disabled: false }, - { name: "Argentina", value: "AR", disabled: false }, - { name: "Armenia", value: "AM", disabled: false }, - { name: "Aruba", value: "AW", disabled: false }, - { name: "Austria", value: "AT", disabled: false }, - { name: "Azerbaijan", value: "AZ", disabled: false }, - { name: "Bahamas", value: "BS", disabled: false }, - { name: "Bahrain", value: "BH", disabled: false }, - { name: "Bangladesh", value: "BD", disabled: false }, - { name: "Barbados", value: "BB", disabled: false }, - { name: "Belarus", value: "BY", disabled: false }, - { name: "Belgium", value: "BE", disabled: false }, - { name: "Belize", value: "BZ", disabled: false }, - { name: "Benin", value: "BJ", disabled: false }, - { name: "Bermuda", value: "BM", disabled: false }, - { name: "Bhutan", value: "BT", disabled: false }, - { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, - { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, - { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, - { name: "Botswana", value: "BW", disabled: false }, - { name: "Bouvet Island", value: "BV", disabled: false }, - { name: "Brazil", value: "BR", disabled: false }, - { name: "British Indian Ocean Territory", value: "IO", disabled: false }, - { name: "Brunei Darussalam", value: "BN", disabled: false }, - { name: "Bulgaria", value: "BG", disabled: false }, - { name: "Burkina Faso", value: "BF", disabled: false }, - { name: "Burundi", value: "BI", disabled: false }, - { name: "Cambodia", value: "KH", disabled: false }, - { name: "Cameroon", value: "CM", disabled: false }, - { name: "Cape Verde", value: "CV", disabled: false }, - { name: "Cayman Islands", value: "KY", disabled: false }, - { name: "Central African Republic", value: "CF", disabled: false }, - { name: "Chad", value: "TD", disabled: false }, - { name: "Chile", value: "CL", disabled: false }, - { name: "Christmas Island", value: "CX", disabled: false }, - { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, - { name: "Colombia", value: "CO", disabled: false }, - { name: "Comoros", value: "KM", disabled: false }, - { name: "Congo", value: "CG", disabled: false }, - { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, - { name: "Cook Islands", value: "CK", disabled: false }, - { name: "Costa Rica", value: "CR", disabled: false }, - { name: "Côte d'Ivoire", value: "CI", disabled: false }, - { name: "Croatia", value: "HR", disabled: false }, - { name: "Cuba", value: "CU", disabled: false }, - { name: "Curaçao", value: "CW", disabled: false }, - { name: "Cyprus", value: "CY", disabled: false }, - { name: "Czech Republic", value: "CZ", disabled: false }, - { name: "Denmark", value: "DK", disabled: false }, - { name: "Djibouti", value: "DJ", disabled: false }, - { name: "Dominica", value: "DM", disabled: false }, - { name: "Dominican Republic", value: "DO", disabled: false }, - { name: "Ecuador", value: "EC", disabled: false }, - { name: "Egypt", value: "EG", disabled: false }, - { name: "El Salvador", value: "SV", disabled: false }, - { name: "Equatorial Guinea", value: "GQ", disabled: false }, - { name: "Eritrea", value: "ER", disabled: false }, - { name: "Estonia", value: "EE", disabled: false }, - { name: "Ethiopia", value: "ET", disabled: false }, - { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, - { name: "Faroe Islands", value: "FO", disabled: false }, - { name: "Fiji", value: "FJ", disabled: false }, - { name: "Finland", value: "FI", disabled: false }, - { name: "French Guiana", value: "GF", disabled: false }, - { name: "French Polynesia", value: "PF", disabled: false }, - { name: "French Southern Territories", value: "TF", disabled: false }, - { name: "Gabon", value: "GA", disabled: false }, - { name: "Gambia", value: "GM", disabled: false }, - { name: "Georgia", value: "GE", disabled: false }, - { name: "Ghana", value: "GH", disabled: false }, - { name: "Gibraltar", value: "GI", disabled: false }, - { name: "Greece", value: "GR", disabled: false }, - { name: "Greenland", value: "GL", disabled: false }, - { name: "Grenada", value: "GD", disabled: false }, - { name: "Guadeloupe", value: "GP", disabled: false }, - { name: "Guam", value: "GU", disabled: false }, - { name: "Guatemala", value: "GT", disabled: false }, - { name: "Guernsey", value: "GG", disabled: false }, - { name: "Guinea", value: "GN", disabled: false }, - { name: "Guinea-Bissau", value: "GW", disabled: false }, - { name: "Guyana", value: "GY", disabled: false }, - { name: "Haiti", value: "HT", disabled: false }, - { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, - { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, - { name: "Honduras", value: "HN", disabled: false }, - { name: "Hong Kong", value: "HK", disabled: false }, - { name: "Hungary", value: "HU", disabled: false }, - { name: "Iceland", value: "IS", disabled: false }, - { name: "Indonesia", value: "ID", disabled: false }, - { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, - { name: "Iraq", value: "IQ", disabled: false }, - { name: "Ireland", value: "IE", disabled: false }, - { name: "Isle of Man", value: "IM", disabled: false }, - { name: "Israel", value: "IL", disabled: false }, - { name: "Italy", value: "IT", disabled: false }, - { name: "Jamaica", value: "JM", disabled: false }, - { name: "Japan", value: "JP", disabled: false }, - { name: "Jersey", value: "JE", disabled: false }, - { name: "Jordan", value: "JO", disabled: false }, - { name: "Kazakhstan", value: "KZ", disabled: false }, - { name: "Kenya", value: "KE", disabled: false }, - { name: "Kiribati", value: "KI", disabled: false }, - { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, - { name: "Korea, Republic of", value: "KR", disabled: false }, - { name: "Kuwait", value: "KW", disabled: false }, - { name: "Kyrgyzstan", value: "KG", disabled: false }, - { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, - { name: "Latvia", value: "LV", disabled: false }, - { name: "Lebanon", value: "LB", disabled: false }, - { name: "Lesotho", value: "LS", disabled: false }, - { name: "Liberia", value: "LR", disabled: false }, - { name: "Libya", value: "LY", disabled: false }, - { name: "Liechtenstein", value: "LI", disabled: false }, - { name: "Lithuania", value: "LT", disabled: false }, - { name: "Luxembourg", value: "LU", disabled: false }, - { name: "Macao", value: "MO", disabled: false }, - { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, - { name: "Madagascar", value: "MG", disabled: false }, - { name: "Malawi", value: "MW", disabled: false }, - { name: "Malaysia", value: "MY", disabled: false }, - { name: "Maldives", value: "MV", disabled: false }, - { name: "Mali", value: "ML", disabled: false }, - { name: "Malta", value: "MT", disabled: false }, - { name: "Marshall Islands", value: "MH", disabled: false }, - { name: "Martinique", value: "MQ", disabled: false }, - { name: "Mauritania", value: "MR", disabled: false }, - { name: "Mauritius", value: "MU", disabled: false }, - { name: "Mayotte", value: "YT", disabled: false }, - { name: "Mexico", value: "MX", disabled: false }, - { name: "Micronesia, Federated States of", value: "FM", disabled: false }, - { name: "Moldova, Republic of", value: "MD", disabled: false }, - { name: "Monaco", value: "MC", disabled: false }, - { name: "Mongolia", value: "MN", disabled: false }, - { name: "Montenegro", value: "ME", disabled: false }, - { name: "Montserrat", value: "MS", disabled: false }, - { name: "Morocco", value: "MA", disabled: false }, - { name: "Mozambique", value: "MZ", disabled: false }, - { name: "Myanmar", value: "MM", disabled: false }, - { name: "Namibia", value: "NA", disabled: false }, - { name: "Nauru", value: "NR", disabled: false }, - { name: "Nepal", value: "NP", disabled: false }, - { name: "Netherlands", value: "NL", disabled: false }, - { name: "New Caledonia", value: "NC", disabled: false }, - { name: "New Zealand", value: "NZ", disabled: false }, - { name: "Nicaragua", value: "NI", disabled: false }, - { name: "Niger", value: "NE", disabled: false }, - { name: "Nigeria", value: "NG", disabled: false }, - { name: "Niue", value: "NU", disabled: false }, - { name: "Norfolk Island", value: "NF", disabled: false }, - { name: "Northern Mariana Islands", value: "MP", disabled: false }, - { name: "Norway", value: "NO", disabled: false }, - { name: "Oman", value: "OM", disabled: false }, - { name: "Pakistan", value: "PK", disabled: false }, - { name: "Palau", value: "PW", disabled: false }, - { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, - { name: "Panama", value: "PA", disabled: false }, - { name: "Papua New Guinea", value: "PG", disabled: false }, - { name: "Paraguay", value: "PY", disabled: false }, - { name: "Peru", value: "PE", disabled: false }, - { name: "Philippines", value: "PH", disabled: false }, - { name: "Pitcairn", value: "PN", disabled: false }, - { name: "Poland", value: "PL", disabled: false }, - { name: "Portugal", value: "PT", disabled: false }, - { name: "Puerto Rico", value: "PR", disabled: false }, - { name: "Qatar", value: "QA", disabled: false }, - { name: "Réunion", value: "RE", disabled: false }, - { name: "Romania", value: "RO", disabled: false }, - { name: "Russian Federation", value: "RU", disabled: false }, - { name: "Rwanda", value: "RW", disabled: false }, - { name: "Saint Barthélemy", value: "BL", disabled: false }, - { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, - { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, - { name: "Saint Lucia", value: "LC", disabled: false }, - { name: "Saint Martin (French part)", value: "MF", disabled: false }, - { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, - { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, - { name: "Samoa", value: "WS", disabled: false }, - { name: "San Marino", value: "SM", disabled: false }, - { name: "Sao Tome and Principe", value: "ST", disabled: false }, - { name: "Saudi Arabia", value: "SA", disabled: false }, - { name: "Senegal", value: "SN", disabled: false }, - { name: "Serbia", value: "RS", disabled: false }, - { name: "Seychelles", value: "SC", disabled: false }, - { name: "Sierra Leone", value: "SL", disabled: false }, - { name: "Singapore", value: "SG", disabled: false }, - { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, - { name: "Slovakia", value: "SK", disabled: false }, - { name: "Slovenia", value: "SI", disabled: false }, - { name: "Solomon Islands", value: "SB", disabled: false }, - { name: "Somalia", value: "SO", disabled: false }, - { name: "South Africa", value: "ZA", disabled: false }, - { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, - { name: "South Sudan", value: "SS", disabled: false }, - { name: "Spain", value: "ES", disabled: false }, - { name: "Sri Lanka", value: "LK", disabled: false }, - { name: "Sudan", value: "SD", disabled: false }, - { name: "Suriname", value: "SR", disabled: false }, - { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, - { name: "Swaziland", value: "SZ", disabled: false }, - { name: "Sweden", value: "SE", disabled: false }, - { name: "Switzerland", value: "CH", disabled: false }, - { name: "Syrian Arab Republic", value: "SY", disabled: false }, - { name: "Taiwan", value: "TW", disabled: false }, - { name: "Tajikistan", value: "TJ", disabled: false }, - { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, - { name: "Thailand", value: "TH", disabled: false }, - { name: "Timor-Leste", value: "TL", disabled: false }, - { name: "Togo", value: "TG", disabled: false }, - { name: "Tokelau", value: "TK", disabled: false }, - { name: "Tonga", value: "TO", disabled: false }, - { name: "Trinidad and Tobago", value: "TT", disabled: false }, - { name: "Tunisia", value: "TN", disabled: false }, - { name: "Turkey", value: "TR", disabled: false }, - { name: "Turkmenistan", value: "TM", disabled: false }, - { name: "Turks and Caicos Islands", value: "TC", disabled: false }, - { name: "Tuvalu", value: "TV", disabled: false }, - { name: "Uganda", value: "UG", disabled: false }, - { name: "Ukraine", value: "UA", disabled: false }, - { name: "United Arab Emirates", value: "AE", disabled: false }, - { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, - { name: "Uruguay", value: "UY", disabled: false }, - { name: "Uzbekistan", value: "UZ", disabled: false }, - { name: "Vanuatu", value: "VU", disabled: false }, - { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, - { name: "Viet Nam", value: "VN", disabled: false }, - { name: "Virgin Islands, British", value: "VG", disabled: false }, - { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, - { name: "Wallis and Futuna", value: "WF", disabled: false }, - { name: "Western Sahara", value: "EH", disabled: false }, - { name: "Yemen", value: "YE", disabled: false }, - { name: "Zambia", value: "ZM", disabled: false }, - { name: "Zimbabwe", value: "ZW", disabled: false }, - ]; - taxRates: TaxRateResponse[]; + countryList: CountryListItem[] = this.taxService.getCountries(); constructor( private apiService: ApiService, private route: ActivatedRoute, private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, + private taxService: TaxServiceAbstraction, ) {} get country(): string { - return this.taxFormGroup.get("country").value; - } - - set country(country: string) { - this.taxFormGroup.get("country").setValue(country); + return this.taxFormGroup.controls.country.value; } get postalCode(): string { - return this.taxFormGroup.get("postalCode").value; - } - - set postalCode(postalCode: string) { - this.taxFormGroup.get("postalCode").setValue(postalCode); - } - - get includeTaxId(): boolean { - return this.taxFormGroup.get("includeTaxId").value; - } - - set includeTaxId(includeTaxId: boolean) { - this.taxFormGroup.get("includeTaxId").setValue(includeTaxId); + return this.taxFormGroup.controls.postalCode.value; } get taxId(): string { - return this.taxFormGroup.get("taxId").value; - } - - set taxId(taxId: string) { - this.taxFormGroup.get("taxId").setValue(taxId); + return this.taxFormGroup.controls.taxId.value; } get line1(): string { - return this.taxFormGroup.get("line1").value; - } - - set line1(line1: string) { - this.taxFormGroup.get("line1").setValue(line1); + return this.taxFormGroup.controls.line1.value; } get line2(): string { - return this.taxFormGroup.get("line2").value; - } - - set line2(line2: string) { - this.taxFormGroup.get("line2").setValue(line2); + return this.taxFormGroup.controls.line2.value; } get city(): string { - return this.taxFormGroup.get("city").value; - } - - set city(city: string) { - this.taxFormGroup.get("city").setValue(city); + return this.taxFormGroup.controls.city.value; } get state(): string { - return this.taxFormGroup.get("state").value; + return this.taxFormGroup.controls.state.value; } - set state(state: string) { - this.taxFormGroup.get("state").setValue(state); + get showTaxIdField(): boolean { + return !!this.organizationId; } async ngOnInit() { @@ -400,22 +100,13 @@ export class TaxInfoComponent implements OnInit { try { const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); if (taxInfo) { - this.taxId = taxInfo.taxId; - this.state = taxInfo.state; - this.line1 = taxInfo.line1; - this.line2 = taxInfo.line2; - this.city = taxInfo.city; - this.state = taxInfo.state; - this.postalCode = taxInfo.postalCode; - this.country = taxInfo.country || "US"; - this.includeTaxId = - this.countrySupportsTax(this.country) && - (!!taxInfo.taxId || - !!taxInfo.line1 || - !!taxInfo.line2 || - !!taxInfo.city || - !!taxInfo.state); - this.setTaxInfoObject(); + this.taxFormGroup.controls.taxId.setValue(taxInfo.taxId); + this.taxFormGroup.controls.state.setValue(taxInfo.state); + this.taxFormGroup.controls.line1.setValue(taxInfo.line1); + this.taxFormGroup.controls.line2.setValue(taxInfo.line2); + this.taxFormGroup.controls.city.setValue(taxInfo.city); + this.taxFormGroup.controls.postalCode.setValue(taxInfo.postalCode); + this.taxFormGroup.controls.country.setValue(taxInfo.country); } } catch (e) { this.logService.error(e); @@ -424,119 +115,79 @@ export class TaxInfoComponent implements OnInit { try { const taxInfo = await this.apiService.getTaxInfo(); if (taxInfo) { - this.postalCode = taxInfo.postalCode; - this.country = taxInfo.country || "US"; + this.taxFormGroup.controls.postalCode.setValue(taxInfo.postalCode); + this.taxFormGroup.controls.country.setValue(taxInfo.country); } - this.setTaxInfoObject(); } catch (e) { this.logService.error(e); } } - if (this.country === "US") { - this.taxFormGroup.get("postalCode").setValidators([Validators.required]); - this.taxFormGroup.get("postalCode").updateValueAndValidity(); - } + this.isTaxSupported = await this.taxService.isCountrySupported( + this.taxFormGroup.controls.country.value, + ); - if (this.country !== "US") { - this.onCountryChanged.emit(); - } + this.countryChanged.emit(); }); - this.taxFormGroup - .get("country") - .valueChanges.pipe(takeUntil(this.destroy$)) + this.taxFormGroup.controls.country.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) .subscribe((value) => { - if (value === "US") { - this.taxFormGroup.get("postalCode").setValidators([Validators.required]); - } else { - this.taxFormGroup.get("postalCode").clearValidators(); - } - this.taxFormGroup.get("postalCode").updateValueAndValidity(); - this.setTaxInfoObject(); - this.changeCountry(); + this.taxService + .isCountrySupported(this.taxFormGroup.controls.country.value) + .then((isSupported) => { + this.isTaxSupported = isSupported; + }) + .catch(() => { + this.isTaxSupported = false; + }) + .finally(() => { + if (!this.isTaxSupported) { + this.taxFormGroup.controls.taxId.setValue(null); + this.taxFormGroup.controls.line1.setValue(null); + this.taxFormGroup.controls.line2.setValue(null); + this.taxFormGroup.controls.city.setValue(null); + this.taxFormGroup.controls.state.setValue(null); + } + + this.countryChanged.emit(); + }); + this.taxInformationChanged.emit(); }); - try { - const taxRates = await this.apiService.getTaxRates(); - if (taxRates) { - this.taxRates = taxRates.data; - } - } catch (e) { - this.logService.error(e); - } finally { - this.loading = false; - } - } - - get taxRate() { - if (this.taxRates != null) { - const localTaxRate = this.taxRates.find( - (x) => x.country === this.country && x.postalCode === this.postalCode, - ); - return localTaxRate?.rate ?? null; - } - } - - setTaxInfoObject() { - this.taxInfo.country = this.country; - this.taxInfo.postalCode = this.postalCode; - this.taxInfo.includeTaxId = this.includeTaxId; - this.taxInfo.taxId = this.taxId; - this.taxInfo.line1 = this.line1; - this.taxInfo.line2 = this.line2; - this.taxInfo.city = this.city; - this.taxInfo.state = this.state; - } + this.taxFormGroup.controls.postalCode.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + this.taxInformationChanged.emit(); + }); - get showTaxIdCheckbox() { - return ( - (this.organizationId || this.providerId) && - this.country !== "US" && - this.countrySupportsTax(this.taxInfo.country) - ); - } + this.taxFormGroup.controls.taxId.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + this.taxInformationChanged.emit(); + }); - get showTaxIdFields() { - return ( - (this.organizationId || this.providerId) && - this.includeTaxId && - this.countrySupportsTax(this.country) - ); + this.loading = false; } - getTaxInfoRequest(): TaxInfoUpdateRequest { - if (this.organizationId || this.providerId) { - const request = new ExpandedTaxInfoUpdateRequest(); - request.country = this.country; - request.postalCode = this.postalCode; - - if (this.includeTaxId) { - request.taxId = this.taxId; - request.line1 = this.line1; - request.line2 = this.line2; - request.city = this.city; - request.state = this.state; - } else { - request.taxId = null; - request.line1 = null; - request.line2 = null; - request.city = null; - request.state = null; - } - return request; - } else { - const request = new TaxInfoUpdateRequest(); - request.postalCode = this.postalCode; - request.country = this.country; - return request; - } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } submitTaxInfo(): Promise { this.taxFormGroup.updateValueAndValidity(); this.taxFormGroup.markAllAsTouched(); - const request = this.getTaxInfoRequest(); + + const request = new ExpandedTaxInfoUpdateRequest(); + request.country = this.country; + request.postalCode = this.postalCode; + request.taxId = this.taxId; + request.line1 = this.line1; + request.line2 = this.line2; + request.city = this.city; + request.state = this.state; + return this.organizationId ? this.organizationApiService.updateTaxInfo( this.organizationId, @@ -544,97 +195,4 @@ export class TaxInfoComponent implements OnInit { ) : this.apiService.putTaxInfo(request); } - - changeCountry() { - if (!this.countrySupportsTax(this.country)) { - this.includeTaxId = false; - this.taxId = null; - this.line1 = null; - this.line2 = null; - this.city = null; - this.state = null; - this.setTaxInfoObject(); - } - this.onCountryChanged.emit(); - } - - countrySupportsTax(countryCode: string) { - return this.taxSupportedCountryCodes.includes(countryCode); - } - - private taxSupportedCountryCodes: string[] = [ - "CN", - "FR", - "DE", - "CA", - "GB", - "AU", - "IN", - "AD", - "AR", - "AT", - "BE", - "BO", - "BR", - "BG", - "CL", - "CO", - "CR", - "HR", - "CY", - "CZ", - "DK", - "DO", - "EC", - "EG", - "SV", - "EE", - "FI", - "GE", - "GR", - "HK", - "HU", - "IS", - "ID", - "IQ", - "IE", - "IL", - "IT", - "JP", - "KE", - "KR", - "LV", - "LI", - "LT", - "LU", - "MY", - "MT", - "MX", - "NL", - "NZ", - "NO", - "PE", - "PH", - "PL", - "PT", - "RO", - "RU", - "SA", - "RS", - "SG", - "SK", - "SI", - "ZA", - "ES", - "SE", - "CH", - "TW", - "TH", - "TR", - "UA", - "AE", - "UY", - "VE", - "VN", - ]; } diff --git a/apps/web/src/app/billing/shared/update-license.component.ts b/apps/web/src/app/billing/shared/update-license.component.ts index e5421776846..e580d420202 100644 --- a/apps/web/src/app/billing/shared/update-license.component.ts +++ b/apps/web/src/app/billing/shared/update-license.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; diff --git a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html index 1b09f4bed51..1367e6e3082 100644 --- a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html +++ b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html @@ -1,18 +1,12 @@ - -

{{ "verifyBankAccountDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }}

+ +

{{ "verifyBankAccountWithStatementDescriptorInstructions" | i18n }}

- - {{ "amountX" | i18n: "1" }} - - $0. - - - {{ "amountX" | i18n: "2" }} - - $0. + + {{ "descriptorCode" | i18n }} + -
+ diff --git a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts index 2f9e34f877b..d2cd473d3d3 100644 --- a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts +++ b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; @@ -16,25 +18,17 @@ export class VerifyBankAccountComponent { @Output() submitted = new EventEmitter(); protected formGroup = this.formBuilder.group({ - amount1: new FormControl(null, [ + descriptorCode: new FormControl(null, [ Validators.required, - Validators.min(0), - Validators.max(99), - ]), - amount2: new FormControl(null, [ - Validators.required, - Validators.min(0), - Validators.max(99), + Validators.minLength(6), + Validators.maxLength(6), ]), }); constructor(private formBuilder: FormBuilder) {} submit = async () => { - const request = new VerifyBankAccountRequest( - this.formGroup.value.amount1, - this.formGroup.value.amount2, - ); + const request = new VerifyBankAccountRequest(this.formGroup.value.descriptorCode); await this.onSubmit?.(request); this.submitted.emit(); }; diff --git a/apps/web/src/app/common/base.accept.component.ts b/apps/web/src/app/common/base.accept.component.ts index 4b35eb811c9..4e938fcd081 100644 --- a/apps/web/src/app/common/base.accept.component.ts +++ b/apps/web/src/app/common/base.accept.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { Subject, firstValueFrom } from "rxjs"; diff --git a/apps/web/src/app/components/dynamic-avatar.component.ts b/apps/web/src/app/components/dynamic-avatar.component.ts index 4cdfda4eba8..4381524de66 100644 --- a/apps/web/src/app/components/dynamic-avatar.component.ts +++ b/apps/web/src/app/components/dynamic-avatar.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnDestroy } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.html b/apps/web/src/app/components/environment-selector/environment-selector.component.html index 4be0db2ba45..a1fb1a8a0f3 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.html +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.html @@ -17,7 +17,7 @@
- {{ "server" | i18n }}: + {{ "accessing" | i18n }}: {{ currentRegion?.domain }} diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.ts b/apps/web/src/app/components/environment-selector/environment-selector.component.ts index 132b68c6e2b..b86c068911f 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.ts +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; diff --git a/apps/web/src/app/components/selectable-avatar.component.ts b/apps/web/src/app/components/selectable-avatar.component.ts index 1de722461a9..7a746481563 100644 --- a/apps/web/src/app/components/selectable-avatar.component.ts +++ b/apps/web/src/app/components/selectable-avatar.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; @Component({ diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index d1fa3f8ba8c..8f21dfa2c8b 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { APP_INITIALIZER, NgModule, Optional, SkipSelf } from "@angular/core"; import { Router } from "@angular/router"; @@ -28,8 +30,9 @@ import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/serv import { RegistrationFinishService as RegistrationFinishServiceAbstraction, LoginComponentService, - LockComponentService, SetPasswordJitService, + SsoComponentService, + LoginDecryptionOptionsService, } from "@bitwarden/auth/angular"; import { InternalUserDecryptionOptionsServiceAbstraction, @@ -45,10 +48,10 @@ import { import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; +import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -60,6 +63,7 @@ import { import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -82,7 +86,12 @@ import { } from "@bitwarden/common/platform/theming/theme-state.service"; import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService as KeyServiceAbstraction, BiometricsService } from "@bitwarden/key-management"; +import { + KdfConfigService, + KeyService as KeyServiceAbstraction, + BiometricsService, +} from "@bitwarden/key-management"; +import { LockComponentService } from "@bitwarden/key-management/angular"; import { flagEnabled } from "../../utils/flags"; import { PolicyListService } from "../admin-console/core/policy-list.service"; @@ -90,11 +99,14 @@ import { WebSetPasswordJitService, WebRegistrationFinishService, WebLoginComponentService, - WebLockComponentService, + WebLoginDecryptionOptionsService, } from "../auth"; +import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service"; import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service"; import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; +import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service"; +import { WebProcessReloadService } from "../key-management/services/web-process-reload.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; import { WebMigrationRunner } from "../platform/web-migration-runner"; @@ -281,11 +293,26 @@ const safeProviders: SafeProvider[] = [ useClass: flagEnabled("sdk") ? WebSdkClientFactory : NoopSdkClientFactory, deps: [], }), + safeProvider({ + provide: ProcessReloadServiceAbstraction, + useClass: WebProcessReloadService, + deps: [WINDOW], + }), safeProvider({ provide: LoginEmailService, useClass: LoginEmailService, deps: [AccountService, AuthService, StateProvider], }), + safeProvider({ + provide: SsoComponentService, + useClass: WebSsoComponentService, + deps: [I18nServiceAbstraction], + }), + safeProvider({ + provide: LoginDecryptionOptionsService, + useClass: WebLoginDecryptionOptionsService, + deps: [MessagingService, RouterService, AcceptOrganizationInviteService], + }), ]; @NgModule({ diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index be6a62443d3..aedad9b26ea 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -1,10 +1,14 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { DeviceType, EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @Injectable() @@ -14,6 +18,7 @@ export class EventService { constructor( private i18nService: I18nService, policyService: PolicyService, + private configService: ConfigService, ) { policyService.policies$.subscribe((policies) => { this.policies = policies; @@ -449,10 +454,20 @@ export class EventService { msg = humanReadableMsg = this.i18nService.t("removedDomain", ev.domainName); break; case EventType.OrganizationDomain_Verified: - msg = humanReadableMsg = this.i18nService.t("domainVerifiedEvent", ev.domainName); + msg = humanReadableMsg = this.i18nService.t( + (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) + ? "domainClaimedEvent" + : "domainVerifiedEvent", + ev.domainName, + ); break; case EventType.OrganizationDomain_NotVerified: - msg = humanReadableMsg = this.i18nService.t("domainNotVerifiedEvent", ev.domainName); + msg = humanReadableMsg = this.i18nService.t( + (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) + ? "domainNotClaimedEvent" + : "domainNotVerifiedEvent", + ev.domainName, + ); break; // Secrets Manager case EventType.Secret_Retrieved: diff --git a/apps/web/src/app/core/guards/has-premium.guard.ts b/apps/web/src/app/core/guards/has-premium.guard.ts index ab544dafb61..61853b25cb8 100644 --- a/apps/web/src/app/core/guards/has-premium.guard.ts +++ b/apps/web/src/app/core/guards/has-premium.guard.ts @@ -6,9 +6,10 @@ import { CanActivateFn, UrlTree, } from "@angular/router"; -import { Observable } from "rxjs"; -import { tap } from "rxjs/operators"; +import { Observable, of } from "rxjs"; +import { switchMap, tap } from "rxjs/operators"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -24,8 +25,14 @@ export function hasPremiumGuard(): CanActivateFn { const router = inject(Router); const messagingService = inject(MessagingService); const billingAccountProfileStateService = inject(BillingAccountProfileStateService); + const accountService = inject(AccountService); - return billingAccountProfileStateService.hasPremiumFromAnySource$.pipe( + return accountService.activeAccount$.pipe( + switchMap((account) => + account + ? billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), tap((userHasPremium: boolean) => { if (!userHasPremium) { messagingService.send("premiumRequired"); diff --git a/apps/web/src/app/core/html-storage.service.ts b/apps/web/src/app/core/html-storage.service.ts index d83d4c6fadb..318aed5e9f3 100644 --- a/apps/web/src/app/core/html-storage.service.ts +++ b/apps/web/src/app/core/html-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 4bd0ff1f482..b3e6d691f75 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -17,6 +17,8 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { KeyService as KeyServiceAbstraction } from "@bitwarden/key-management"; +import { VersionService } from "../platform/version.service"; + @Injectable() export class InitService { constructor( @@ -32,6 +34,7 @@ export class InitService { private encryptService: EncryptService, private userAutoUnlockKeyService: UserAutoUnlockKeyService, private accountService: AccountService, + private versionService: VersionService, @Inject(DOCUMENT) private document: Document, ) {} @@ -54,6 +57,8 @@ export class InitService { const htmlEl = this.win.document.documentElement; htmlEl.classList.add("locale_" + this.i18nService.translationLocale); this.themingService.applyThemeChangesTo(this.document); + this.versionService.applyVersionToWindow(); + const containerService = new ContainerService(this.keyService, this.encryptService); containerService.attachToGlobal(this.win); }; diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts index 07901628481..ff0aea47b9a 100644 --- a/apps/web/src/app/core/router.service.ts +++ b/apps/web/src/app/core/router.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Title } from "@angular/platform-browser"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; diff --git a/apps/web/src/app/core/web-platform-utils.service.spec.ts b/apps/web/src/app/core/web-platform-utils.service.spec.ts index 5b0271b8227..3b5cb96b718 100644 --- a/apps/web/src/app/core/web-platform-utils.service.spec.ts +++ b/apps/web/src/app/core/web-platform-utils.service.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { WebPlatformUtilsService } from "./web-platform-utils.service"; describe("Web Platform Utils Service", () => { diff --git a/apps/web/src/app/core/web-platform-utils.service.ts b/apps/web/src/app/core/web-platform-utils.service.ts index dbd0ef593d6..3df2a7d895b 100644 --- a/apps/web/src/app/core/web-platform-utils.service.ts +++ b/apps/web/src/app/core/web-platform-utils.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { ClientType, DeviceType } from "@bitwarden/common/enums"; diff --git a/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts b/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts index 81b7d361579..d407a709d4c 100644 --- a/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts +++ b/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common"; import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request"; import { SendWithIdRequest } from "@bitwarden/common/src/tools/send/models/request/send-with-id.request"; diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index 41215d012aa..4f2ae8f77e0 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 6c2f15a02a6..ae47798420e 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; diff --git a/apps/web/src/app/auth/core/services/web-lock-component.service.spec.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts similarity index 95% rename from apps/web/src/app/auth/core/services/web-lock-component.service.spec.ts rename to apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts index 5eb26a8c76c..3c941fe24c7 100644 --- a/apps/web/src/app/auth/core/services/web-lock-component.service.spec.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts @@ -4,6 +4,7 @@ import { firstValueFrom, of } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; +import { BiometricsStatus } from "@bitwarden/key-management"; import { WebLockComponentService } from "./web-lock-component.service"; @@ -86,7 +87,7 @@ describe("WebLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: null, + biometricsStatus: BiometricsStatus.PlatformUnsupported, }, }); }); diff --git a/apps/web/src/app/auth/core/services/web-lock-component.service.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts similarity index 90% rename from apps/web/src/app/auth/core/services/web-lock-component.service.ts rename to apps/web/src/app/key-management/lock/services/web-lock-component.service.ts index e24f299e23b..02910966d6e 100644 --- a/apps/web/src/app/auth/core/services/web-lock-component.service.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts @@ -1,12 +1,13 @@ import { inject } from "@angular/core"; import { map, Observable } from "rxjs"; -import { LockComponentService, UnlockOptions } from "@bitwarden/auth/angular"; import { UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; +import { BiometricsStatus } from "@bitwarden/key-management"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; export class WebLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); @@ -45,7 +46,7 @@ export class WebLockComponentService implements LockComponentService { }, biometrics: { enabled: false, - disableReason: null, + biometricsStatus: BiometricsStatus.PlatformUnsupported, }, }; return unlockOpts; diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts index 68ef95fef6f..2361293fe80 100644 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts +++ b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom } from "rxjs"; @@ -81,7 +83,7 @@ export class MigrateFromLegacyEncryptionComponent { }); if (deleteFolders) { - await this.folderApiService.deleteAll(); + await this.folderApiService.deleteAll(activeUser.id); await this.syncService.fullSync(true, true); await this.submit(); return; diff --git a/apps/web/src/app/key-management/services/web-process-reload.service.ts b/apps/web/src/app/key-management/services/web-process-reload.service.ts new file mode 100644 index 00000000000..c542c97c0e0 --- /dev/null +++ b/apps/web/src/app/key-management/services/web-process-reload.service.ts @@ -0,0 +1,14 @@ +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; + +export class WebProcessReloadService implements ProcessReloadServiceAbstraction { + constructor(private window: Window) {} + + async startProcessReload(authService: AuthService): Promise { + this.window.location.reload(); + } + + cancelProcessReload(): void { + return; + } +} diff --git a/apps/web/src/app/key-management/web-biometric.service.ts b/apps/web/src/app/key-management/web-biometric.service.ts index 4681eb6fa49..0c58c0da759 100644 --- a/apps/web/src/app/key-management/web-biometric.service.ts +++ b/apps/web/src/app/key-management/web-biometric.service.ts @@ -1,27 +1,27 @@ -import { BiometricsService } from "@bitwarden/key-management"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; export class WebBiometricsService extends BiometricsService { - async supportsBiometric(): Promise { + async authenticateWithBiometrics(): Promise { return false; } - async isBiometricUnlockAvailable(): Promise { - return false; + async getBiometricsStatus(): Promise { + return BiometricsStatus.PlatformUnsupported; } - async authenticateBiometric(): Promise { - throw new Error("Method not implemented."); + async unlockWithBiometricsForUser(userId: UserId): Promise { + return null; } - async biometricsNeedsSetup(): Promise { - throw new Error("Method not implemented."); + async getBiometricsStatusForUser(userId: UserId): Promise { + return BiometricsStatus.PlatformUnsupported; } - async biometricsSupportsAutoSetup(): Promise { - throw new Error("Method not implemented."); + async getShouldAutopromptNow(): Promise { + return false; } - async biometricsSetup(): Promise { - throw new Error("Method not implemented."); - } + async setShouldAutopromptNow(value: boolean): Promise {} } diff --git a/apps/web/src/app/layouts/frontend-layout.component.ts b/apps/web/src/app/layouts/frontend-layout.component.ts index e4d947c1c6a..609845f22cd 100644 --- a/apps/web/src/app/layouts/frontend-layout.component.ts +++ b/apps/web/src/app/layouts/frontend-layout.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/apps/web/src/app/layouts/header/web-header.component.ts b/apps/web/src/app/layouts/header/web-header.component.ts index 94f57f95802..2f0c9d4772b 100644 --- a/apps/web/src/app/layouts/header/web-header.component.ts +++ b/apps/web/src/app/layouts/header/web-header.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { map, Observable } from "rxjs"; diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts index bbfa8eb396f..b09b32d060e 100644 --- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts +++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index a07f56db2d7..382ce8e026b 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -6,7 +6,7 @@ import { BehaviorSubject } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { BitIconButtonComponent } from "@bitwarden/components/src/icon-button/icon-button.component"; +import { IconButtonModule, NavigationModule } from "@bitwarden/components"; import { NavItemComponent } from "@bitwarden/components/src/navigation/nav-item.component"; import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-switcher.service"; @@ -45,13 +45,8 @@ describe("NavigationProductSwitcherComponent", () => { mockProducts$.next({ bento: [], other: [] }); await TestBed.configureTestingModule({ - imports: [RouterModule], - declarations: [ - NavigationProductSwitcherComponent, - NavItemComponent, - BitIconButtonComponent, - I18nPipe, - ], + imports: [RouterModule, NavigationModule, IconButtonModule], + declarations: [NavigationProductSwitcherComponent, I18nPipe], providers: [ { provide: ProductSwitcherService, useValue: productSwitcherService }, { diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index cd1a77a9ec4..a7ff50b4264 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -152,7 +152,13 @@ export const SMAvailable: Story = { ...Template, args: { mockOrgs: [ - { id: "org-a", canManageUsers: false, canAccessSecretsManager: true, enabled: true }, + { + id: "org-a", + canManageUsers: false, + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => false, + }, ] as Organization[], mockProviders: [], }, @@ -162,7 +168,13 @@ export const SMAndACAvailable: Story = { ...Template, args: { mockOrgs: [ - { id: "org-a", canManageUsers: true, canAccessSecretsManager: true, enabled: true }, + { + id: "org-a", + canManageUsers: true, + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => false, + }, ] as Organization[], mockProviders: [], }, @@ -172,7 +184,13 @@ export const WithAllOptions: Story = { ...Template, args: { mockOrgs: [ - { id: "org-a", canManageUsers: true, canAccessSecretsManager: true, enabled: true }, + { + id: "org-a", + canManageUsers: true, + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => false, + }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], }, diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html index 41346675bbb..204737eee2e 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html @@ -17,12 +17,12 @@ ? 'tw-bg-primary-600 tw-font-bold !tw-text-contrast tw-ring-offset-2 hover:tw-bg-primary-600' : '' " - class="tw-group tw-flex tw-h-24 tw-w-28 tw-flex-col tw-items-center tw-justify-center tw-rounded tw-p-1 tw-text-primary-600 tw-outline-none hover:tw-bg-background-alt hover:tw-text-primary-700 hover:tw-no-underline focus-visible:!tw-ring-2 focus-visible:!tw-ring-primary-700" + class="tw-group/product-link tw-flex tw-h-24 tw-w-28 tw-flex-col tw-items-center tw-justify-center tw-rounded tw-p-1 tw-text-primary-600 tw-outline-none hover:tw-bg-background-alt hover:tw-text-primary-700 hover:tw-no-underline focus-visible:!tw-ring-2 focus-visible:!tw-ring-primary-700" ariaCurrentWhenActive="page" > {{ product.name }} diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts index 5e199e24b12..4a22f628570 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, ViewChild } from "@angular/core"; import { MenuComponent } from "@bitwarden/components"; diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index b9d1d394920..b53d0243f64 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -171,7 +171,13 @@ export const WithSM: Story = { ...Template, args: { mockOrgs: [ - { id: "org-a", canManageUsers: false, canAccessSecretsManager: true, enabled: true }, + { + id: "org-a", + canManageUsers: false, + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => false, + }, ] as Organization[], mockProviders: [], }, @@ -181,7 +187,13 @@ export const WithSMAndAC: Story = { ...Template, args: { mockOrgs: [ - { id: "org-a", canManageUsers: true, canAccessSecretsManager: true, enabled: true }, + { + id: "org-a", + canManageUsers: true, + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => false, + }, ] as Organization[], mockProviders: [], }, @@ -191,7 +203,13 @@ export const WithAllOptions: Story = { ...Template, args: { mockOrgs: [ - { id: "org-a", canManageUsers: true, canAccessSecretsManager: true, enabled: true }, + { + id: "org-a", + canManageUsers: true, + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => false, + }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], }, diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index 07a41e7c94c..a071d0f8852 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TestBed } from "@angular/core/testing"; import { ActivatedRoute, Router, convertToParamMap } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; @@ -110,7 +112,12 @@ describe("ProductSwitcherService", () => { it("is included in bento when there is an organization with SM", async () => { organizationService.organizations$ = of([ - { id: "1234", canAccessSecretsManager: true, enabled: true }, + { + id: "1234", + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => true, + }, ] as Organization[]); initiateService(); @@ -220,8 +227,20 @@ describe("ProductSwitcherService", () => { router.url = "/sm/4243"; organizationService.organizations$ = of([ - { id: "23443234", canAccessSecretsManager: true, enabled: true, name: "Org 2" }, - { id: "4243", canAccessSecretsManager: true, enabled: true, name: "Org 32" }, + { + id: "23443234", + canAccessSecretsManager: true, + enabled: true, + name: "Org 2", + canAccessExport: (_) => true, + }, + { + id: "4243", + canAccessSecretsManager: true, + enabled: true, + name: "Org 32", + canAccessExport: (_) => true, + }, ] as Organization[]); initiateService(); diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index 28474d792a5..2c16886a2d4 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { ActivatedRoute, NavigationEnd, NavigationStart, ParamMap, Router } from "@angular/router"; import { combineLatest, concatMap, filter, map, Observable, ReplaySubject, startWith } from "rxjs"; diff --git a/apps/web/src/app/layouts/user-layout.component.html b/apps/web/src/app/layouts/user-layout.component.html index 4011ac84a75..6a87658f172 100644 --- a/apps/web/src/app/layouts/user-layout.component.html +++ b/apps/web/src/app/layouts/user-layout.component.html @@ -24,11 +24,7 @@ [text]="'emergencyAccess' | i18n" route="settings/emergency-access" > - + diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index bd025332335..f0ac3ef9b48 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -1,16 +1,18 @@ +// 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 { RouterModule } from "@angular/router"; -import { Observable, combineLatest, concatMap } from "rxjs"; +import { Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { IconModule } from "@bitwarden/components"; +import { BillingFreeFamiliesNavItemComponent } from "../billing/shared/billing-free-families-nav-item.component"; + import { PasswordManagerLogo } from "./password-manager-logo"; import { WebLayoutModule } from "./web-layout.module"; @@ -18,46 +20,36 @@ import { WebLayoutModule } from "./web-layout.module"; selector: "app-user-layout", templateUrl: "user-layout.component.html", standalone: true, - imports: [CommonModule, RouterModule, JslibModule, WebLayoutModule, IconModule], + imports: [ + CommonModule, + RouterModule, + JslibModule, + WebLayoutModule, + IconModule, + BillingFreeFamiliesNavItemComponent, + ], }) export class UserLayoutComponent implements OnInit { protected readonly logo = PasswordManagerLogo; + isFreeFamilyFlagEnabled: boolean; protected hasFamilySponsorshipAvailable$: Observable; + protected showSponsoredFamilies$: Observable; protected showSubscription$: Observable; constructor( - private platformUtilsService: PlatformUtilsService, - private organizationService: OrganizationService, - private apiService: ApiService, private syncService: SyncService, private billingAccountProfileStateService: BillingAccountProfileStateService, - ) {} + private accountService: AccountService, + ) { + this.showSubscription$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.canViewSubscription$(account.id), + ), + ); + } async ngOnInit() { document.body.classList.remove("layout_frontend"); - await this.syncService.fullSync(false); - - this.hasFamilySponsorshipAvailable$ = this.organizationService.canManageSponsorships$; - - // We want to hide the subscription menu for organizations that provide premium. - // Except if the user has premium personally or has a billing history. - this.showSubscription$ = combineLatest([ - this.billingAccountProfileStateService.hasPremiumPersonally$, - this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$, - ]).pipe( - concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => { - const isCloud = !this.platformUtilsService.isSelfHost(); - - let billing = null; - if (isCloud) { - // TODO: We should remove the need to call this! - billing = await this.apiService.getUserBillingHistory(); - } - - const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory; - return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory; - }), - ); } } diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index b208bb3f8d1..fadcc28f832 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { Route, RouterModule, Routes } from "@angular/router"; +import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component"; import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap"; import { authGuard, @@ -11,7 +12,7 @@ import { } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap"; -import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; +import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, @@ -24,15 +25,26 @@ import { RegistrationLinkExpiredComponent, LoginComponent, LoginSecondaryContentComponent, - LockV2Component, LockIcon, + TwoFactorTimeoutIcon, UserLockIcon, + SsoKeyIcon, + LoginViaAuthRequestComponent, + DevicesIcon, RegistrationUserAddIcon, RegistrationLockAltIcon, RegistrationExpiredLinkIcon, + SsoComponent, VaultIcon, + LoginDecryptionOptionsComponent, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { LockComponent } from "@bitwarden/key-management/angular"; +import { + NewDeviceVerificationNoticePageOneComponent, + NewDeviceVerificationNoticePageTwoComponent, + VaultIcons, +} from "@bitwarden/vault"; import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { flagEnabled, Flags } from "../utils/flags"; @@ -43,10 +55,9 @@ import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizatio import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { deepLinkGuard } from "./auth/guards/deep-link.guard"; import { HintComponent } from "./auth/hint.component"; -import { LockComponent } from "./auth/lock.component"; -import { LoginDecryptionOptionsComponent } from "./auth/login/login-decryption-options/login-decryption-options.component"; +import { LoginDecryptionOptionsComponentV1 } from "./auth/login/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "./auth/login/login-v1.component"; -import { LoginViaAuthRequestComponent } from "./auth/login/login-via-auth-request.component"; +import { LoginViaAuthRequestComponentV1 } from "./auth/login/login-via-auth-request-v1.component"; import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login-via-webauthn.component"; import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component"; import { RecoverDeleteComponent } from "./auth/recover-delete.component"; @@ -57,7 +68,7 @@ import { AccountComponent } from "./auth/settings/account/account.component"; import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component"; import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component"; import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module"; -import { SsoComponent } from "./auth/sso.component"; +import { SsoComponentV1 } from "./auth/sso-v1.component"; import { CompleteTrialInitiationComponent } from "./auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; import { freeTrialTextResolver } from "./auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component"; @@ -96,31 +107,16 @@ const routes: Routes = [ children: [], // Children lets us have an empty component. canActivate: [redirectGuard()], // Redirects either to vault, login, or lock page. }, - { - path: "login-with-device", - component: LoginViaAuthRequestComponent, - data: { titleId: "loginWithDevice" } satisfies RouteDataProperties, - }, { path: "login-with-passkey", component: LoginViaWebAuthnComponent, data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties, }, - { - path: "admin-approval-requested", - component: LoginViaAuthRequestComponent, - data: { titleId: "adminApprovalRequested" } satisfies RouteDataProperties, - }, - { - path: "login-initiated", - component: LoginDecryptionOptionsComponent, - canActivate: [tdeDecryptionRequiredGuard()], - }, { path: "register", component: TrialInitiationComponent, canActivate: [ - canAccessFeature(FeatureFlag.EmailVerification, false, "/signup"), + canAccessFeature(FeatureFlag.EmailVerification, false, "/signup", false), unauthGuardFn(), ], data: { titleId: "createAccount" } satisfies RouteDataProperties, @@ -179,6 +175,57 @@ const routes: Routes = [ }, ], }, + ...unauthUiRefreshSwap( + LoginViaAuthRequestComponentV1, + AnonLayoutWrapperComponent, + { + path: "login-with-device", + data: { titleId: "loginWithDevice" } satisfies RouteDataProperties, + }, + { + path: "login-with-device", + data: { + pageIcon: DevicesIcon, + pageTitle: { + key: "loginInitiated", + }, + pageSubtitle: { + key: "aNotificationWasSentToYourDevice", + }, + titleId: "loginInitiated", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { path: "", component: LoginViaAuthRequestComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + ), + ...unauthUiRefreshSwap( + LoginViaAuthRequestComponentV1, + AnonLayoutWrapperComponent, + { + path: "admin-approval-requested", + data: { titleId: "adminApprovalRequested" } satisfies RouteDataProperties, + }, + { + path: "admin-approval-requested", + data: { + pageIcon: DevicesIcon, + pageTitle: { + key: "adminApprovalRequested", + }, + pageSubtitle: { + key: "adminApprovalRequestSentToAdmins", + }, + titleId: "adminApprovalRequested", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [{ path: "", component: LoginViaAuthRequestComponent }], + }, + ), ...unauthUiRefreshSwap( AnonLayoutWrapperComponent, AnonLayoutWrapperComponent, @@ -229,6 +276,22 @@ const routes: Routes = [ ], }, ), + ...unauthUiRefreshSwap( + LoginDecryptionOptionsComponentV1, + AnonLayoutWrapperComponent, + { + path: "login-initiated", + canActivate: [tdeDecryptionRequiredGuard()], + }, + { + path: "login-initiated", + canActivate: [tdeDecryptionRequiredGuard()], + data: { + pageIcon: DevicesIcon, + }, + children: [{ path: "", component: LoginDecryptionOptionsComponent }], + }, + ), ...unauthUiRefreshSwap( AnonLayoutWrapperComponent, AnonLayoutWrapperComponent, @@ -373,19 +436,64 @@ const routes: Routes = [ }, ], }, + ...unauthUiRefreshSwap( + SsoComponentV1, + SsoComponent, + { + path: "sso", + canActivate: [unauthGuardFn()], + data: { + pageTitle: { + key: "enterpriseSingleSignOn", + }, + titleId: "enterpriseSingleSignOn", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { + path: "", + component: SsoComponentV1, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + { + path: "sso", + canActivate: [unauthGuardFn()], + data: { + pageTitle: { + key: "singleSignOn", + }, + titleId: "enterpriseSingleSignOn", + pageSubtitle: { + key: "singleSignOnEnterOrgIdentifierText", + }, + titleAreaMaxWidth: "md", + pageIcon: SsoKeyIcon, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { + path: "", + component: SsoComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + ), { - path: "sso", + path: "login", canActivate: [unauthGuardFn()], - data: { - pageTitle: { - key: "enterpriseSingleSignOn", - }, - titleId: "enterpriseSingleSignOn", - } satisfies RouteDataProperties & AnonLayoutWrapperData, children: [ { path: "", - component: SsoComponent, + component: LoginComponent, }, { path: "", @@ -393,15 +501,36 @@ const routes: Routes = [ outlet: "environment-selector", }, ], + data: { + pageTitle: { + key: "logIn", + }, + }, }, { - path: "login", - canActivate: [unauthGuardFn()], + path: "lock", + canActivate: [deepLinkGuard(), lockGuard()], children: [ { path: "", - component: LoginComponent, + component: LockComponent, + }, + ], + data: { + pageTitle: { + key: "yourVaultIsLockedV2", }, + pageIcon: LockIcon, + showReadonlyHostname: true, + } satisfies AnonLayoutWrapperData, + }, + { + path: "2fa", + canActivate: [unauthGuardFn()], + children: [ + ...twofactorRefactorSwap(TwoFactorComponent, TwoFactorAuthComponent, { + path: "", + }), { path: "", component: EnvironmentSelectorComponent, @@ -410,56 +539,18 @@ const routes: Routes = [ ], data: { pageTitle: { - key: "logIn", + key: "verifyIdentity", }, - }, + } satisfies RouteDataProperties & AnonLayoutWrapperData, }, - ...extensionRefreshSwap( - LockComponent, - LockV2Component, - { - path: "lock", - canActivate: [deepLinkGuard(), lockGuard()], - children: [ - { - path: "", - component: LockComponent, - }, - ], - data: { - pageTitle: { - key: "yourVaultIsLockedV2", - }, - pageIcon: LockIcon, - showReadonlyHostname: true, - } satisfies AnonLayoutWrapperData, - }, - { - path: "lock", - canActivate: [deepLinkGuard(), lockGuard()], - children: [ - { - path: "", - component: LockV2Component, - }, - ], - data: { - pageTitle: { - key: "yourAccountIsLocked", - }, - pageIcon: LockIcon, - showReadonlyHostname: true, - } satisfies AnonLayoutWrapperData, - }, - ), - { - path: "2fa", + path: "2fa-timeout", canActivate: [unauthGuardFn()], children: [ - ...twofactorRefactorSwap(TwoFactorComponent, TwoFactorAuthComponent, { + { path: "", - }), + component: TwoFactorTimeoutComponent, + }, { path: "", component: EnvironmentSelectorComponent, @@ -467,9 +558,11 @@ const routes: Routes = [ }, ], data: { + pageIcon: TwoFactorTimeoutIcon, pageTitle: { - key: "verifyIdentity", + key: "authenticationTimeout", }, + titleId: "authenticationTimeout", } satisfies RouteDataProperties & AnonLayoutWrapperData, }, { @@ -585,10 +678,37 @@ const routes: Routes = [ }, ], }, + { + path: "new-device-notice", + component: AnonLayoutWrapperComponent, + canActivate: [], + children: [ + { + path: "", + component: NewDeviceVerificationNoticePageOneComponent, + data: { + pageIcon: VaultIcons.ExclamationTriangle, + pageTitle: { + key: "importantNotice", + }, + }, + }, + { + path: "setup", + component: NewDeviceVerificationNoticePageTwoComponent, + data: { + pageIcon: VaultIcons.UserLock, + pageTitle: { + key: "setupTwoStepLogin", + }, + }, + }, + ], + }, { path: "", component: UserLayoutComponent, - canActivate: [deepLinkGuard(), authGuard], + canActivate: [deepLinkGuard(), authGuard, NewDeviceVerificationNoticeGuard], children: [ { path: "vault", diff --git a/apps/web/src/app/platform/version.service.ts b/apps/web/src/app/platform/version.service.ts new file mode 100644 index 00000000000..83b8d30b1a9 --- /dev/null +++ b/apps/web/src/app/platform/version.service.ts @@ -0,0 +1,36 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Injectable } from "@angular/core"; +import { catchError, firstValueFrom, map } from "rxjs"; + +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; + +type Version = { + client: string; + sdk: string; +}; + +@Injectable({ + providedIn: "root", +}) +export class VersionService { + constructor( + private platformUtilsService: PlatformUtilsService, + private sdkService: SdkService, + ) {} + + applyVersionToWindow() { + (window as any).__version = async (): Promise => { + return { + client: await this.platformUtilsService.getApplicationVersion(), + sdk: await firstValueFrom( + this.sdkService.client$.pipe( + map((client) => client.version()), + catchError(() => "Unsupported"), + ), + ), + }; + }; + } +} diff --git a/apps/web/src/app/platform/web-environment.service.spec.ts b/apps/web/src/app/platform/web-environment.service.spec.ts index 14b5e7dcaa0..9d0140b6786 100644 --- a/apps/web/src/app/platform/web-environment.service.spec.ts +++ b/apps/web/src/app/platform/web-environment.service.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; diff --git a/apps/web/src/app/platform/web-environment.service.ts b/apps/web/src/app/platform/web-environment.service.ts index ebddc7491ba..1df842d6b31 100644 --- a/apps/web/src/app/platform/web-environment.service.ts +++ b/apps/web/src/app/platform/web-environment.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Router } from "@angular/router"; import { firstValueFrom, ReplaySubject } from "rxjs"; diff --git a/apps/web/src/app/platform/web-migration-runner.spec.ts b/apps/web/src/app/platform/web-migration-runner.spec.ts index c27be4a145e..be9cabe8d52 100644 --- a/apps/web/src/app/platform/web-migration-runner.spec.ts +++ b/apps/web/src/app/platform/web-migration-runner.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MockProxy, mock } from "jest-mock-extended"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/web/src/app/platform/web-sdk-client-factory.ts b/apps/web/src/app/platform/web-sdk-client-factory.ts index 2ebb2bcc10f..0dd43ecbb92 100644 --- a/apps/web/src/app/platform/web-sdk-client-factory.ts +++ b/apps/web/src/app/platform/web-sdk-client-factory.ts @@ -27,6 +27,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts b/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts index fb580b93ee4..805745c369d 100644 --- a/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts +++ b/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Guid } from "@bitwarden/common/src/types/guid"; export class RequestSMAccessRequest { diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts index 890cbe8ca12..8909df6cf8a 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts index d932e289663..3698031a5b6 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; diff --git a/apps/web/src/app/settings/domain-rules.component.html b/apps/web/src/app/settings/domain-rules.component.html index 1c8e5e435ec..a3bea63fb86 100644 --- a/apps/web/src/app/settings/domain-rules.component.html +++ b/apps/web/src/app/settings/domain-rules.component.html @@ -54,7 +54,7 @@

{{ "globalEqDomains" | i18n }}

- + {{ d.domains }} diff --git a/apps/web/src/app/tools/credential-generator/credential-generator.component.ts b/apps/web/src/app/tools/credential-generator/credential-generator.component.ts index 9eb4b0a0814..8d7b56a09ad 100644 --- a/apps/web/src/app/tools/credential-generator/credential-generator.component.ts +++ b/apps/web/src/app/tools/credential-generator/credential-generator.component.ts @@ -1,6 +1,10 @@ import { Component } from "@angular/core"; -import { GeneratorModule } from "@bitwarden/generator-components"; +import { ButtonModule, DialogService, LinkModule } from "@bitwarden/components"; +import { + CredentialGeneratorHistoryDialogComponent, + GeneratorModule, +} from "@bitwarden/generator-components"; import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; @@ -9,6 +13,12 @@ import { SharedModule } from "../../shared"; standalone: true, selector: "credential-generator", templateUrl: "credential-generator.component.html", - imports: [SharedModule, HeaderModule, GeneratorModule], + imports: [SharedModule, HeaderModule, GeneratorModule, ButtonModule, LinkModule], }) -export class CredentialGeneratorComponent {} +export class CredentialGeneratorComponent { + constructor(private dialogService: DialogService) {} + + openHistoryDialog = () => { + this.dialogService.open(CredentialGeneratorHistoryDialogComponent); + }; +} diff --git a/apps/web/src/app/tools/event-export/event-export.service.ts b/apps/web/src/app/tools/event-export/event-export.service.ts index 68df258b130..f39b786b6d1 100644 --- a/apps/web/src/app/tools/event-export/event-export.service.ts +++ b/apps/web/src/app/tools/event-export/event-export.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import * as papa from "papaparse"; diff --git a/apps/web/src/app/tools/generator.component.ts b/apps/web/src/app/tools/generator.component.ts index f462e8fa4bf..a11c0c4a97b 100644 --- a/apps/web/src/app/tools/generator.component.ts +++ b/apps/web/src/app/tools/generator.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, NgZone } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; diff --git a/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts index 5df2ffd0f27..1574389a1a0 100644 --- a/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore // eslint-disable-next-line no-restricted-imports import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ReactiveFormsModule } from "@angular/forms"; diff --git a/apps/web/src/app/tools/reports/pages/breach-report.component.ts b/apps/web/src/app/tools/reports/pages/breach-report.component.ts index 177dcac4f2f..e1da7be06f8 100644 --- a/apps/web/src/app/tools/reports/pages/breach-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/breach-report.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; diff --git a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts index cfefbbe4d74..b1a46bd13a8 100644 --- a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core"; import { BehaviorSubject, Observable, Subject, takeUntil } from "rxjs"; diff --git a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts index 5cfe2cd1a9e..792ad0616f2 100644 --- a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; diff --git a/apps/web/src/app/tools/reports/pages/reports-home.component.ts b/apps/web/src/app/tools/reports/pages/reports-home.component.ts index 541193fafab..604d66f6858 100644 --- a/apps/web/src/app/tools/reports/pages/reports-home.component.ts +++ b/apps/web/src/app/tools/reports/pages/reports-home.component.ts @@ -1,6 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { reports, ReportType } from "../reports"; @@ -13,11 +16,15 @@ import { ReportEntry, ReportVariant } from "../shared"; export class ReportsHomeComponent implements OnInit { reports: ReportEntry[]; - constructor(private billingAccountProfileStateService: BillingAccountProfileStateService) {} + constructor( + private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, + ) {} async ngOnInit(): Promise { + const account = await firstValueFrom(this.accountService.activeAccount$); const userHasPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ); const reportRequiresPremium = userHasPremium ? ReportVariant.Enabled diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts index 70cb2ed69b3..a8806acea13 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts index 26ba4885e68..f3ad6840c8b 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; diff --git a/apps/web/src/app/tools/reports/report-utils.ts b/apps/web/src/app/tools/reports/report-utils.ts index 58211e7ae58..460fc937f72 100644 --- a/apps/web/src/app/tools/reports/report-utils.ts +++ b/apps/web/src/app/tools/reports/report-utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as papa from "papaparse"; /** diff --git a/apps/web/src/app/tools/reports/shared/report-card/report-card.component.ts b/apps/web/src/app/tools/reports/shared/report-card/report-card.component.ts index 13a2a04e0d2..da42d92bf84 100644 --- a/apps/web/src/app/tools/reports/shared/report-card/report-card.component.ts +++ b/apps/web/src/app/tools/reports/shared/report-card/report-card.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { Icon } from "@bitwarden/components"; diff --git a/apps/web/src/app/tools/reports/shared/report-list/report-list.component.ts b/apps/web/src/app/tools/reports/shared/report-list/report-list.component.ts index f2d9c3501bd..cd6b77f9c81 100644 --- a/apps/web/src/app/tools/reports/shared/report-list/report-list.component.ts +++ b/apps/web/src/app/tools/reports/shared/report-list/report-list.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { ReportEntry } from "../models/report-entry"; diff --git a/apps/web/src/app/tools/risk-insights/all-applications.component.ts b/apps/web/src/app/tools/risk-insights/all-applications.component.ts deleted file mode 100644 index 5d76403f46b..00000000000 --- a/apps/web/src/app/tools/risk-insights/all-applications.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Component, DestroyRef, inject, OnInit } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl } from "@angular/forms"; -import { ActivatedRoute } from "@angular/router"; -import { debounceTime, firstValueFrom, map } from "rxjs"; - -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -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 { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { - Icons, - NoItemsModule, - SearchModule, - TableDataSource, - ToastService, -} from "@bitwarden/components"; -import { CardComponent } from "@bitwarden/tools-card"; - -import { HeaderModule } from "../../layouts/header/header.module"; -import { SharedModule } from "../../shared"; -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; - -import { applicationTableMockData } from "./application-table.mock"; - -@Component({ - standalone: true, - selector: "tools-all-applications", - templateUrl: "./all-applications.component.html", - imports: [HeaderModule, CardComponent, SearchModule, PipesModule, NoItemsModule, SharedModule], -}) -export class AllApplicationsComponent implements OnInit { - protected dataSource = new TableDataSource(); - protected selectedIds: Set = new Set(); - protected searchControl = new FormControl("", { nonNullable: true }); - private destroyRef = inject(DestroyRef); - protected loading = false; - protected organization: Organization; - noItemsIcon = Icons.Security; - protected markingAsCritical = false; - isCritialAppsFeatureEnabled = false; - - // MOCK DATA - protected mockData = applicationTableMockData; - protected mockAtRiskMembersCount = 0; - protected mockAtRiskAppsCount = 0; - protected mockTotalMembersCount = 0; - protected mockTotalAppsCount = 0; - - async ngOnInit() { - this.activatedRoute.paramMap - .pipe( - takeUntilDestroyed(this.destroyRef), - map(async (params) => { - const organizationId = params.get("organizationId"); - this.organization = await firstValueFrom(this.organizationService.get$(organizationId)); - // TODO: use organizationId to fetch data - }), - ) - .subscribe(); - - this.isCritialAppsFeatureEnabled = await this.configService.getFeatureFlag( - FeatureFlag.CriticalApps, - ); - } - - constructor( - protected cipherService: CipherService, - protected passwordStrengthService: PasswordStrengthServiceAbstraction, - protected auditService: AuditService, - protected i18nService: I18nService, - protected activatedRoute: ActivatedRoute, - protected toastService: ToastService, - protected organizationService: OrganizationService, - protected configService: ConfigService, - ) { - this.dataSource.data = applicationTableMockData; - this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe((v) => (this.dataSource.filter = v)); - } - - goToCreateNewLoginItem = async () => { - // TODO: implement - this.toastService.showToast({ - variant: "warning", - title: null, - message: "Not yet implemented", - }); - }; - - markAppsAsCritical = async () => { - // TODO: Send to API once implemented - this.markingAsCritical = true; - return new Promise((resolve) => { - setTimeout(() => { - this.selectedIds.clear(); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("appsMarkedAsCritical"), - }); - resolve(true); - this.markingAsCritical = false; - }, 1000); - }); - }; - - trackByFunction(_: number, item: CipherView) { - return item.id; - } - - onCheckboxChange(id: number, event: Event) { - const isChecked = (event.target as HTMLInputElement).checked; - if (isChecked) { - this.selectedIds.add(id); - } else { - this.selectedIds.delete(id); - } - } -} diff --git a/apps/web/src/app/tools/risk-insights/password-health.component.ts b/apps/web/src/app/tools/risk-insights/password-health.component.ts deleted file mode 100644 index c3c1732854d..00000000000 --- a/apps/web/src/app/tools/risk-insights/password-health.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, inject, OnInit } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute } from "@angular/router"; -import { map } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -// eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { - BadgeModule, - BadgeVariant, - ContainerComponent, - TableDataSource, - TableModule, -} from "@bitwarden/components"; - -// eslint-disable-next-line no-restricted-imports -import { HeaderModule } from "../../layouts/header/header.module"; -// eslint-disable-next-line no-restricted-imports -import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; -// eslint-disable-next-line no-restricted-imports -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; - -@Component({ - standalone: true, - selector: "tools-password-health", - templateUrl: "password-health.component.html", - imports: [ - BadgeModule, - OrganizationBadgeModule, - CommonModule, - ContainerComponent, - PipesModule, - JslibModule, - HeaderModule, - TableModule, - ], - providers: [PasswordHealthService], -}) -export class PasswordHealthComponent implements OnInit { - passwordStrengthMap = new Map(); - - passwordUseMap = new Map(); - - exposedPasswordMap = new Map(); - - dataSource = new TableDataSource(); - - loading = true; - - private destroyRef = inject(DestroyRef); - - constructor( - protected cipherService: CipherService, - protected passwordStrengthService: PasswordStrengthServiceAbstraction, - protected auditService: AuditService, - protected i18nService: I18nService, - protected activatedRoute: ActivatedRoute, - ) {} - - ngOnInit() { - this.activatedRoute.paramMap - .pipe( - takeUntilDestroyed(this.destroyRef), - map(async (params) => { - const organizationId = params.get("organizationId"); - await this.setCiphers(organizationId); - }), - ) - .subscribe(); - } - - async setCiphers(organizationId: string) { - const passwordHealthService = new PasswordHealthService( - this.passwordStrengthService, - this.auditService, - this.cipherService, - organizationId, - ); - - await passwordHealthService.generateReport(); - - this.dataSource.data = passwordHealthService.reportCiphers; - this.exposedPasswordMap = passwordHealthService.exposedPasswordMap; - this.passwordStrengthMap = passwordHealthService.passwordStrengthMap; - this.passwordUseMap = passwordHealthService.passwordUseMap; - this.loading = false; - } -} diff --git a/apps/web/src/app/tools/risk-insights/risk-insights.component.html b/apps/web/src/app/tools/risk-insights/risk-insights.component.html deleted file mode 100644 index c2cd0cac707..00000000000 --- a/apps/web/src/app/tools/risk-insights/risk-insights.component.html +++ /dev/null @@ -1,46 +0,0 @@ -
{{ "riskInsights" | i18n }}
-

{{ "passwordRisk" | i18n }}

-
{{ "discoverAtRiskPasswords" | i18n }}
-
- - {{ - "dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a") - }} - - {{ "refresh" | i18n }} - -
- - - - - - - - {{ "criticalApplicationsWithCount" | i18n: criticalApps.length }} - - - - - - - - - - - - - - diff --git a/apps/web/src/app/tools/risk-insights/risk-insights.component.ts b/apps/web/src/app/tools/risk-insights/risk-insights.component.ts deleted file mode 100644 index 1c6a36b4454..00000000000 --- a/apps/web/src/app/tools/risk-insights/risk-insights.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router } from "@angular/router"; - -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 { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; - -import { HeaderModule } from "../../layouts/header/header.module"; - -import { AllApplicationsComponent } from "./all-applications.component"; -import { CriticalApplicationsComponent } from "./critical-applications.component"; -import { NotifiedMembersTableComponent } from "./notified-members-table.component"; -import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component"; -import { PasswordHealthMembersComponent } from "./password-health-members.component"; -import { PasswordHealthComponent } from "./password-health.component"; - -export enum RiskInsightsTabType { - AllApps = 0, - CriticalApps = 1, - NotifiedMembers = 2, -} - -@Component({ - standalone: true, - templateUrl: "./risk-insights.component.html", - imports: [ - AllApplicationsComponent, - AsyncActionsModule, - ButtonModule, - CommonModule, - CriticalApplicationsComponent, - JslibModule, - HeaderModule, - PasswordHealthComponent, - PasswordHealthMembersComponent, - PasswordHealthMembersURIComponent, - NotifiedMembersTableComponent, - TabsModule, - ], -}) -export class RiskInsightsComponent implements OnInit { - tabIndex: RiskInsightsTabType; - dataLastUpdated = new Date(); - isCritialAppsFeatureEnabled = false; - - apps: any[] = []; - criticalApps: any[] = []; - notifiedMembers: any[] = []; - - async refreshData() { - // TODO: Implement - return new Promise((resolve) => - setTimeout(() => { - this.dataLastUpdated = new Date(); - resolve(true); - }, 1000), - ); - } - - onTabChange = async (newIndex: number) => { - await this.router.navigate([], { - relativeTo: this.route, - queryParams: { tabIndex: newIndex }, - queryParamsHandling: "merge", - }); - }; - - async ngOnInit() { - this.isCritialAppsFeatureEnabled = await this.configService.getFeatureFlag( - FeatureFlag.CriticalApps, - ); - } - - constructor( - protected route: ActivatedRoute, - private router: Router, - private configService: ConfigService, - ) { - route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { - this.tabIndex = !isNaN(tabIndex) ? tabIndex : RiskInsightsTabType.AllApps; - }); - } -} diff --git a/apps/web/src/app/tools/risk-insights/risk-insights.module.ts b/apps/web/src/app/tools/risk-insights/risk-insights.module.ts deleted file mode 100644 index 23d3cd8089b..00000000000 --- a/apps/web/src/app/tools/risk-insights/risk-insights.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { RiskInsightsRoutingModule } from "./risk-insights-routing.module"; -import { RiskInsightsComponent } from "./risk-insights.component"; - -@NgModule({ - imports: [RiskInsightsComponent, RiskInsightsRoutingModule], -}) -export class RiskInsightsModule {} diff --git a/apps/web/src/app/tools/send/access.component.ts b/apps/web/src/app/tools/send/access.component.ts index 3c64ee90ca1..80439acd510 100644 --- a/apps/web/src/app/tools/send/access.component.ts +++ b/apps/web/src/app/tools/send/access.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; diff --git a/apps/web/src/app/tools/send/add-edit.component.ts b/apps/web/src/app/tools/send/add-edit.component.ts index 4dc3001831d..4ce126a33bc 100644 --- a/apps/web/src/app/tools/send/add-edit.component.ts +++ b/apps/web/src/app/tools/send/add-edit.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { DatePipe } from "@angular/common"; import { Component, Inject } from "@angular/core"; diff --git a/apps/web/src/app/tools/send/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access-file.component.ts index 1efabb5fec7..b55e955f355 100644 --- a/apps/web/src/app/tools/send/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access-file.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -68,6 +70,8 @@ export class SendAccessFileComponent { blobData: decBuf, downloadMethod: "save", }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/tools/send/send-access-password.component.ts b/apps/web/src/app/tools/send/send-access-password.component.ts index 63c012c3b69..bd98e9d18c8 100644 --- a/apps/web/src/app/tools/send/send-access-password.component.ts +++ b/apps/web/src/app/tools/send/send-access-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; diff --git a/apps/web/src/app/tools/send/send-access-text.component.ts b/apps/web/src/app/tools/send/send-access-text.component.ts index 290bde50cd9..6568fe482ad 100644 --- a/apps/web/src/app/tools/send/send-access-text.component.ts +++ b/apps/web/src/app/tools/send/send-access-text.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { FormBuilder } from "@angular/forms"; diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index e4c7fd7a5fa..1268e4bfb50 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, NgZone, ViewChild, OnInit, OnDestroy, ViewContainerRef } from "@angular/core"; import { lastValueFrom } from "rxjs"; diff --git a/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts b/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts index 4bbbda94a09..53f9f1b2cd7 100644 --- a/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts +++ b/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index a51d408bc74..42d033dc4c2 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { AbstractControl, FormBuilder, Validators } from "@angular/forms"; @@ -29,7 +31,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { BitValidators, DialogService } from "@bitwarden/components"; -import { GroupService, GroupView } from "../../../admin-console/organizations/core"; +import { GroupApiService, GroupView } from "../../../admin-console/organizations/core"; import { PermissionMode } from "../../../admin-console/organizations/shared/components/access-selector/access-selector.component"; import { AccessItemType, @@ -101,7 +103,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private dialogRef: DialogRef, private organizationService: OrganizationService, - private groupService: GroupService, + private groupService: GroupApiService, private collectionAdminService: CollectionAdminService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, diff --git a/apps/web/src/app/vault/components/premium-badge.stories.ts b/apps/web/src/app/vault/components/premium-badge.stories.ts index 17622dbbd5f..331f72fd0ac 100644 --- a/apps/web/src/app/vault/components/premium-badge.stories.ts +++ b/apps/web/src/app/vault/components/premium-badge.stories.ts @@ -2,6 +2,7 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessageSender } from "@bitwarden/common/platform/messaging"; @@ -22,6 +23,14 @@ export default { moduleMetadata({ imports: [JslibModule, BadgeModule], providers: [ + { + provide: AccountService, + useValue: { + activeAccount$: of({ + id: "123", + }), + }, + }, { provide: I18nService, useFactory: () => { @@ -39,7 +48,7 @@ export default { { provide: BillingAccountProfileStateService, useValue: { - hasPremiumFromAnySource$: of(false), + hasPremiumFromAnySource$: () => of(false), }, }, ], diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index df575cc525f..59638aad653 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -1,15 +1,19 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, Observable, Subject } from "rxjs"; +import { firstValueFrom, Observable, Subject, switchMap } from "rxjs"; import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -36,6 +40,7 @@ import { CipherFormGenerationService, CipherFormModule, CipherViewComponent, + DecryptionFailureDialogComponent, } from "@bitwarden/vault"; import { SharedModule } from "../../../shared/shared.module"; @@ -110,6 +115,7 @@ export enum VaultItemDialogResult { CipherAttachmentsComponent, AsyncActionsModule, ItemModule, + DecryptionFailureDialogComponent, ], providers: [ { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, @@ -179,7 +185,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { * Flag to indicate if the user has access to attachments via a premium subscription. * @protected */ - protected canAccessAttachments$ = this.billingAccountProfileStateService.hasPremiumFromAnySource$; + protected canAccessAttachments$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); protected get loadingForm() { return this.loadForm && !this.formReady; @@ -235,6 +245,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { private premiumUpgradeService: PremiumUpgradePromptService, private cipherAuthorizationService: CipherAuthorizationService, private apiService: ApiService, + private eventCollectionService: EventCollectionService, ) { this.updateTitle(); } @@ -243,6 +254,14 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.cipher = await this.getDecryptedCipherView(this.formConfig); if (this.cipher) { + if (this.cipher.decryptionFailure) { + this.dialogService.open(DecryptionFailureDialogComponent, { + data: { cipherIds: [this.cipher.id] }, + }); + this.dialogRef.close(); + return; + } + this.collections = this.formConfig.collections.filter((c) => this.cipher.collectionIds?.includes(c.id), ); @@ -255,6 +274,13 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { [this.params.activeCollectionId], this.params.isAdminConsoleAction, ); + + await this.eventCollectionService.collect( + EventType.Cipher_ClientViewed, + this.cipher.id, + false, + this.cipher.organizationId, + ); } this.performingInitialLoad = false; @@ -281,17 +307,19 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { // If the cipher was newly created (via add/clone), switch the form to edit for subsequent edits. if (this._originalFormMode === "add" || this._originalFormMode === "clone") { this.formConfig.mode = "edit"; + this.formConfig.initialValues = null; } - let cipher: Cipher; + let cipher = await this.cipherService.get(cipherView.id); - // When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint - if (this.formConfig.isAdminConsole) { + // When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state) + if (this.formConfig.isAdminConsole && (cipher == null || this.formConfig.admin)) { const cipherResponse = await this.apiService.getCipherAdmin(cipherView.id); + cipherResponse.edit = true; + cipherResponse.viewPassword = true; + const cipherData = new CipherData(cipherResponse); cipher = new Cipher(cipherData); - } else { - cipher = await this.cipherService.get(cipherView.id); } // Store the updated cipher so any following edits use the most up to date cipher @@ -415,16 +443,19 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { switch (type) { case CipherType.Login: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLowerCase()); + this.title = this.i18nService.t(partOne, this.i18nService.t("typeLogin")); break; case CipherType.Card: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeCard").toLowerCase()); + this.title = this.i18nService.t(partOne, this.i18nService.t("typeCard")); break; case CipherType.Identity: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLowerCase()); + this.title = this.i18nService.t(partOne, this.i18nService.t("typeIdentity")); break; case CipherType.SecureNote: - this.title = this.i18nService.t(partOne, this.i18nService.t("note").toLowerCase()); + this.title = this.i18nService.t(partOne, this.i18nService.t("note")); + break; + case CipherType.SshKey: + this.title = this.i18nService.t(partOne, this.i18nService.t("typeSshKey")); break; } } @@ -466,12 +497,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { * Helper method to delete cipher. */ private async deleteCipher(): Promise { - const cipherIsUnassigned = - !this.cipher.collectionIds || this.cipher.collectionIds?.length === 0; + const cipherIsUnassigned = this.cipher.isUnassigned; // Delete the cipher as an admin when: - // - the organization allows for owners/admins to manage all collections/items - // - the cipher is unassigned + // - The organization allows for owners/admins to manage all collections/items + // - The cipher is unassigned const asAdmin = this.organization?.canEditAllCiphers || cipherIsUnassigned; if (this.cipher.isDeleted) { diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 5c4de576ead..7e59853851c 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -4,7 +4,7 @@ type="checkbox" bitCheckbox appStopProp - [disabled]="disabled" + [disabled]="disabled || cipher.decryptionFailure" [checked]="checked" (change)="$event ? this.checkedToggled.next() : null" [attr.aria-label]="'vaultItemSelect' | i18n" @@ -20,7 +20,7 @@ class="tw-overflow-hidden tw-text-ellipsis tw-text-start tw-leading-snug" [disabled]="disabled" [routerLink]="[]" - [queryParams]="{ itemId: cipher.id, action: extensionRefreshEnabled ? 'view' : null }" + [queryParams]="{ itemId: cipher.id, action: clickAction }" queryParamsHandling="merge" [replaceUrl]="extensionRefreshEnabled" title="{{ 'editItemWithName' | i18n: cipher.name }}" @@ -76,6 +76,25 @@ + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + +
+ + - - - - - - - - - - - - - - - -
- - + + +
diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts index a3d564a9a3a..d28148c49dc 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Router } from "@angular/router"; @@ -57,7 +59,7 @@ export class VaultHeaderComponent implements OnInit { */ @Input() loading: boolean; - /** Current active fitler */ + /** Current active filter */ @Input() filter: RoutedVaultFilterModel; /** The organization currently being viewed */ diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index 9e9264e77cd..512f97144de 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -10,15 +10,27 @@ {{ freeTrial.message }} - {{ "routeToPaymentMethodTrigger" | i18n }} + {{ "clickHereToAddPaymentMethod" | i18n }} + + + {{ resellerWarning?.message }} + + | undefined; protected isEmpty: boolean; protected showCollectionAccessRestricted: boolean; + private hasSubscription$ = new BehaviorSubject(false); protected currentSearchText$: Observable; protected freeTrial$: Observable; + protected resellerWarning$: Observable; + protected prevCipherId: string | null = null; /** * A list of collections that the user can assign items to and edit those items within. * @protected @@ -196,11 +210,17 @@ export class VaultComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); protected addAccessStatus$ = new BehaviorSubject(0); private extensionRefreshEnabled: boolean; + private resellerManagedOrgAlert: boolean; private vaultItemDialogRef?: DialogRef | undefined; + private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( filter((organizations) => organizations.length === 1), - switchMap(([organization]) => + map(([organization]) => organization), + switchMap((organization) => from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( + tap((organizationMetaData) => { + this.hasSubscription$.next(organizationMetaData.hasSubscription); + }), switchMap((organizationMetaData) => from( this.trialFlowService.handleUnpaidSubscriptionDialog( @@ -234,7 +254,7 @@ export class VaultComponent implements OnInit, OnDestroy { private collectionAdminService: CollectionAdminService, private searchService: SearchService, private searchPipe: SearchPipe, - private groupService: GroupService, + private groupService: GroupApiService, private logService: LogService, private eventCollectionService: EventCollectionService, private totpService: TotpService, @@ -246,6 +266,8 @@ export class VaultComponent implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private trialFlowService: TrialFlowService, protected billingApiService: BillingApiServiceAbstraction, + private organizationBillingService: OrganizationBillingServiceAbstraction, + private resellerWarningService: ResellerWarningService, ) {} async ngOnInit() { @@ -253,6 +275,10 @@ export class VaultComponent implements OnInit, OnDestroy { FeatureFlag.ExtensionRefresh, ); + this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( + FeatureFlag.ResellerManagedOrgAlert, + ); + this.trashCleanupWarning = this.i18nService.t( this.platformUtilsService.isSelfHost() ? "trashCleanupWarningSelfHosted" @@ -512,19 +538,38 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( - switchMap(() => this.route.queryParams), - // Only process the queryParams if the dialog is not open (only when extension refresh is enabled) + switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])), filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), - withLatestFrom(allCipherMap$, allCollections$, organization$), switchMap(async ([qParams, allCiphersMap]) => { const cipherId = getCipherIdFromParams(qParams); + if (!cipherId) { + this.prevCipherId = null; + return; + } + + if (cipherId === this.prevCipherId) { return; } - const cipher = allCiphersMap[cipherId]; + this.prevCipherId = cipherId; + + const cipher = allCiphersMap[cipherId]; if (cipher) { let action = qParams.action; + + if (action == "showFailedToDecrypt") { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: [cipherId as CipherId], + }); + await this.router.navigate([], { + queryParams: { itemId: null, cipherId: null, action: null }, + queryParamsHandling: "merge", + replaceUrl: true, + }); + return; + } + // Default to "view" if extension refresh is enabled if (action == null && this.extensionRefreshEnabled) { action = "view"; @@ -580,24 +625,35 @@ export class VaultComponent implements OnInit, OnDestroy { this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe(); - this.freeTrial$ = organization$.pipe( - filter((org) => org.isOwner), - switchMap((org) => + this.freeTrial$ = combineLatest([ + organization$, + this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)), + ]).pipe( + filter( + ([org, hasSubscription]) => org.isOwner && hasSubscription && org.canViewBillingHistory, + ), + switchMap(([org]) => combineLatest([ of(org), this.organizationApiService.getSubscription(org.id), - this.organizationApiService.getBilling(org.id), + this.organizationBillingService.getPaymentSource(org.id), ]), ), - map(([org, sub, billing]) => { - return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( - org, - sub, - billing?.paymentSource, - ); + map(([org, sub, paymentSource]) => { + return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource); }), ); + this.resellerWarning$ = organization$.pipe( + filter((org) => org.isOwner && this.resellerManagedOrgAlert), + switchMap((org) => + from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe( + map((metadata) => ({ org, metadata })), + ), + ), + map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)), + ); + firstSetup$ .pipe( switchMap(() => this.refresh$), @@ -750,29 +806,18 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - let madeAttachmentChanges = false; - - const [modal] = await this.modalService.openViewRef( - AttachmentsComponent, - this.attachmentsModalRef, - (comp) => { - comp.organization = this.organization; - comp.cipherId = cipher.id; - comp.onUploadedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - comp.onDeletedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - }, - ); - - modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { - if (madeAttachmentChanges) { - this.refresh(); - } - madeAttachmentChanges = false; + const dialogRef = AttachmentsV2Component.open(this.dialogService, { + cipherId: cipher.id as CipherId, }); + + const result = await firstValueFrom(dialogRef.closed); + + if ( + result.action === AttachmentDialogResult.Removed || + result.action === AttachmentDialogResult.Uploaded + ) { + this.refresh(); + } } async addCipher(cipherType?: CipherType) { @@ -1136,6 +1181,8 @@ export class VaultComponent implements OnInit, OnDestroy { // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === collection.id) { + // Clear the cipher cache to clear the deleted collection from the cipher state + await this.cipherService.clear(); void this.router.navigate([], { queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, queryParamsHandling: "merge", diff --git a/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts b/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts index cfa0b28dbf0..8fa51e34e2a 100644 --- a/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts +++ b/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject, Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts b/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts index 2c5fb82c53c..a4f73ed1a2e 100644 --- a/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts +++ b/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts @@ -1,7 +1,7 @@ import { Overlay } from "@angular/cdk/overlay"; import { TestBed } from "@angular/core/testing"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { openPasswordHistoryDialog } from "../individual-vault/password-history.component"; @@ -35,10 +35,10 @@ describe("WebViewPasswordHistoryService", () => { describe("viewPasswordHistory", () => { it("calls openPasswordHistoryDialog with the correct parameters", async () => { - const mockCipherId = "cipher-id" as CipherId; - await service.viewPasswordHistory(mockCipherId); + const mockCipher = { id: "cipher-id" } as CipherView; + await service.viewPasswordHistory(mockCipher); expect(openPasswordHistoryDialog).toHaveBeenCalledWith(dialogService, { - data: { cipherId: mockCipherId }, + data: { cipher: mockCipher }, }); }); }); diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.ts b/apps/web/src/app/vault/services/web-view-password-history.service.ts index cbdc3928e60..756c2140ab5 100644 --- a/apps/web/src/app/vault/services/web-view-password-history.service.ts +++ b/apps/web/src/app/vault/services/web-view-password-history.service.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { ViewPasswordHistoryService } from "../../../../../../libs/common/src/vault/abstractions/view-password-history.service"; @@ -17,7 +17,7 @@ export class WebViewPasswordHistoryService implements ViewPasswordHistoryService * Opens the password history dialog for the given cipher ID. * @param cipherId The ID of the cipher to view the password history for. */ - async viewPasswordHistory(cipherId: CipherId) { - openPasswordHistoryDialog(this.dialogService, { data: { cipherId } }); + async viewPasswordHistory(cipher: CipherView) { + openPasswordHistoryDialog(this.dialogService, { data: { cipher } }); } } diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 9a677af7b5d..80b0448a39c 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; diff --git a/apps/web/src/app/vault/utils/collection-utils.ts b/apps/web/src/app/vault/utils/collection-utils.ts index dacd6547c4e..2926ff3acee 100644 --- a/apps/web/src/app/vault/utils/collection-utils.ts +++ b/apps/web/src/app/vault/utils/collection-utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionAdminView, CollectionView, diff --git a/apps/web/src/connectors/captcha.ts b/apps/web/src/connectors/captcha.ts index 0362c9121f5..aad6eaa3d47 100644 --- a/apps/web/src/connectors/captcha.ts +++ b/apps/web/src/connectors/captcha.ts @@ -1,10 +1,16 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { b64Decode, getQsParam } from "./common"; declare let hcaptcha: any; if (window.location.pathname.includes("mobile")) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha-mobile.scss"); } else { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha.scss"); } @@ -48,6 +54,8 @@ async function start() { let decodedData: any; try { decodedData = JSON.parse(b64Decode(data, true)); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 2b8a3de4de1..b5300ff65e7 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -1,6 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { getQsParam } from "./common"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./duo-redirect.scss"); const mobileDesktopCallback = "bitwarden://duo-callback"; @@ -51,14 +55,12 @@ window.addEventListener("load", async () => { */ function redirectToDuoFrameless(redirectUrl: string) { const validateUrl = new URL(redirectUrl); + const validDuoUrl = + validateUrl.protocol === "https:" && + (validateUrl.hostname.endsWith(".duosecurity.com") || + validateUrl.hostname.endsWith(".duofederal.com")); - if ( - validateUrl.protocol !== "https:" || - !( - validateUrl.hostname.endsWith("duosecurity.com") || - validateUrl.hostname.endsWith("duofederal.com") - ) - ) { + if (!validDuoUrl) { throw new Error("Invalid redirect URL"); } diff --git a/apps/web/src/connectors/sso.ts b/apps/web/src/connectors/sso.ts index e049c64e5d9..b48c2b49d72 100644 --- a/apps/web/src/connectors/sso.ts +++ b/apps/web/src/connectors/sso.ts @@ -1,5 +1,9 @@ -import { getQsParam } from "./common"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { getQsParam } from "./common"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./sso.scss"); window.addEventListener("load", () => { diff --git a/apps/web/src/connectors/translation.service.ts b/apps/web/src/connectors/translation.service.ts index 266ded7ce9c..309693081bc 100644 --- a/apps/web/src/connectors/translation.service.ts +++ b/apps/web/src/connectors/translation.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TranslationService as BaseTranslationService } from "@bitwarden/common/platform/services/translation.service"; import { SupportedTranslationLocales } from "../translation-constants"; diff --git a/apps/web/src/connectors/webauthn-fallback.ts b/apps/web/src/connectors/webauthn-fallback.ts index 980a8b3dbf4..6f32bbaecf8 100644 --- a/apps/web/src/connectors/webauthn-fallback.ts +++ b/apps/web/src/connectors/webauthn-fallback.ts @@ -1,7 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); let parsed = false; @@ -50,6 +54,8 @@ function parseParametersV2() { let dataObj: { data: any; btnText: string } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -101,6 +107,8 @@ function start() { let json: any; try { json = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/webauthn.ts b/apps/web/src/connectors/webauthn.ts index f3979bf62e6..32e6e0ab673 100644 --- a/apps/web/src/connectors/webauthn.ts +++ b/apps/web/src/connectors/webauthn.ts @@ -1,6 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); const mobileCallbackUri = "bitwarden://webauthn-callback"; @@ -86,6 +90,8 @@ function parseParametersV2() { } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -114,6 +120,8 @@ function start() { try { obj = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse webauthn data."); return; diff --git a/apps/web/src/images/integrations/aws-color.svg b/apps/web/src/images/integrations/aws-color.svg new file mode 100644 index 00000000000..963b65027db --- /dev/null +++ b/apps/web/src/images/integrations/aws-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/aws-darkmode.svg b/apps/web/src/images/integrations/aws-darkmode.svg new file mode 100644 index 00000000000..64c9ba3cf94 --- /dev/null +++ b/apps/web/src/images/integrations/aws-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/azure-active-directory.svg b/apps/web/src/images/integrations/azure-active-directory.svg new file mode 100644 index 00000000000..22ea64f1f03 --- /dev/null +++ b/apps/web/src/images/integrations/azure-active-directory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/bitwarden-vertical-blue.svg b/apps/web/src/images/integrations/bitwarden-vertical-blue.svg new file mode 100644 index 00000000000..5d5200364d8 --- /dev/null +++ b/apps/web/src/images/integrations/bitwarden-vertical-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/jumpcloud-darkmode.svg b/apps/web/src/images/integrations/jumpcloud-darkmode.svg new file mode 100644 index 00000000000..6969fceeb84 --- /dev/null +++ b/apps/web/src/images/integrations/jumpcloud-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-auth0-badge-color.svg b/apps/web/src/images/integrations/logo-auth0-badge-color.svg new file mode 100644 index 00000000000..24887cc7510 --- /dev/null +++ b/apps/web/src/images/integrations/logo-auth0-badge-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-duo-color.svg b/apps/web/src/images/integrations/logo-duo-color.svg new file mode 100644 index 00000000000..0959a215708 --- /dev/null +++ b/apps/web/src/images/integrations/logo-duo-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-elastic-badge-color.svg b/apps/web/src/images/integrations/logo-elastic-badge-color.svg new file mode 100644 index 00000000000..f6e00f3d40d --- /dev/null +++ b/apps/web/src/images/integrations/logo-elastic-badge-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-google-badge-color.svg b/apps/web/src/images/integrations/logo-google-badge-color.svg new file mode 100644 index 00000000000..c5a8fe50363 --- /dev/null +++ b/apps/web/src/images/integrations/logo-google-badge-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-jumpcloud-badge-color.svg b/apps/web/src/images/integrations/logo-jumpcloud-badge-color.svg new file mode 100644 index 00000000000..9349186d8a1 --- /dev/null +++ b/apps/web/src/images/integrations/logo-jumpcloud-badge-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-keycloak-icon.svg b/apps/web/src/images/integrations/logo-keycloak-icon.svg new file mode 100644 index 00000000000..862ffcb6c2b --- /dev/null +++ b/apps/web/src/images/integrations/logo-keycloak-icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/src/images/integrations/logo-microsoft-entra-id-color.svg b/apps/web/src/images/integrations/logo-microsoft-entra-id-color.svg new file mode 100644 index 00000000000..a6150c29c62 --- /dev/null +++ b/apps/web/src/images/integrations/logo-microsoft-entra-id-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-microsoft-intune-color.svg b/apps/web/src/images/integrations/logo-microsoft-intune-color.svg new file mode 100644 index 00000000000..2611cf4b3b8 --- /dev/null +++ b/apps/web/src/images/integrations/logo-microsoft-intune-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-microsoft-sentinel-color.svg b/apps/web/src/images/integrations/logo-microsoft-sentinel-color.svg new file mode 100644 index 00000000000..93135526c6f --- /dev/null +++ b/apps/web/src/images/integrations/logo-microsoft-sentinel-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-okta-symbol-black.svg b/apps/web/src/images/integrations/logo-okta-symbol-black.svg new file mode 100644 index 00000000000..876727ad56d --- /dev/null +++ b/apps/web/src/images/integrations/logo-okta-symbol-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-onelogin-badge-color.svg b/apps/web/src/images/integrations/logo-onelogin-badge-color.svg new file mode 100644 index 00000000000..e2d9ccbc0c1 --- /dev/null +++ b/apps/web/src/images/integrations/logo-onelogin-badge-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-panther-round-color.svg b/apps/web/src/images/integrations/logo-panther-round-color.svg new file mode 100644 index 00000000000..bed05507681 --- /dev/null +++ b/apps/web/src/images/integrations/logo-panther-round-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-ping-identity-badge-color.svg b/apps/web/src/images/integrations/logo-ping-identity-badge-color.svg new file mode 100644 index 00000000000..e34762c249c --- /dev/null +++ b/apps/web/src/images/integrations/logo-ping-identity-badge-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-rapid7-black.svg b/apps/web/src/images/integrations/logo-rapid7-black.svg new file mode 100644 index 00000000000..e2bb7a6f4a8 --- /dev/null +++ b/apps/web/src/images/integrations/logo-rapid7-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-splunk-black.svg b/apps/web/src/images/integrations/logo-splunk-black.svg new file mode 100644 index 00000000000..d25247bfca8 --- /dev/null +++ b/apps/web/src/images/integrations/logo-splunk-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/okta-darkmode.svg b/apps/web/src/images/integrations/okta-darkmode.svg new file mode 100644 index 00000000000..e16e0d3c700 --- /dev/null +++ b/apps/web/src/images/integrations/okta-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/onelogin-darkmode.svg b/apps/web/src/images/integrations/onelogin-darkmode.svg new file mode 100644 index 00000000000..764b1684faa --- /dev/null +++ b/apps/web/src/images/integrations/onelogin-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/rapid7-darkmode.svg b/apps/web/src/images/integrations/rapid7-darkmode.svg new file mode 100644 index 00000000000..b5f25aae8bd --- /dev/null +++ b/apps/web/src/images/integrations/rapid7-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/splunk-darkmode.svg b/apps/web/src/images/integrations/splunk-darkmode.svg new file mode 100644 index 00000000000..a4515c0a18c --- /dev/null +++ b/apps/web/src/images/integrations/splunk-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/loading-white.svg b/apps/web/src/images/loading-white.svg index 30239140069..2bebff7daba 100644 --- a/apps/web/src/images/loading-white.svg +++ b/apps/web/src/images/loading-white.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/images/loading.svg b/apps/web/src/images/loading.svg index 70763105168..3f2033303db 100644 --- a/apps/web/src/images/loading.svg +++ b/apps/web/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/index.html b/apps/web/src/index.html index ce1a955b88c..0b8ea864914 100644 --- a/apps/web/src/index.html +++ b/apps/web/src/index.html @@ -15,6 +15,7 @@ +
Bitwarden
diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 7837baaed5b..1dd2d62f2a5 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Beveiligde nota" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Aantekeninge" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Teken aan met hoofwagwoord" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "’n Kennisgewing is na u toestel gestuur." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Weergawe $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Wagwoordgeskiedenis" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Daar is geen wagwoorde om te lys nie." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Wis", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Teken asb. weer aan." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Teken asb. weer aan. Indien u ander Bitwarden-toepassings gebruik, teken daarop ook weer uit en aan." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Toestel" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Werk Blaaier By" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "U gebruik ’n onondersteunde webblaaier. Die webkluis werk dalk nie soos normaal nie." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Sluit aan by organisasie" }, @@ -4388,6 +4515,9 @@ "message": "Moenie weer vra om die vingerafdrukfrase te bevestig nie", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Teken vinnig aan d.m.v. u organisasie se enkelaantekenportaal (SSO). Voer u organisasie se identifiseerder in om te begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Onderneming-enkelaanteken" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Uitgesluit, nie van toepassing vir hierdie aksie" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Vingerafdruk" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Borgskap is verwyder" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Vereis indien Entiteit-ID nie ’n bronadres is nie." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Opsionele aanpassings" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Gebruikersnaamtipe" }, @@ -6681,6 +6857,10 @@ "message": "Voorsien gebruikers en groepe outomaties met u voorkeuridentiteitsverskaffer d.m.v. SCIM-bevoorrading", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Aktiveer SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Keur versoek goed" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Geen toestelversoeke" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisasie-inligting" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Stel Github Actions op" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Stel GitLab CI/CD op" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Stel Ansible op" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index d3230826059..2c2549e2f1e 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -524,10 +536,10 @@ "description": "Search Secure Note type" }, "searchVault": { - "message": "البحث في الخزنة" + "message": "البحث في الخزانة" }, "searchMyVault": { - "message": "البحث في خزنتي" + "message": "البحث في خزانتي" }, "searchOrganization": { "message": "البحث عن المؤسسة" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "ملاحظة سرية" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "تسجيلات الدخول" }, @@ -783,19 +798,19 @@ "message": "أنا" }, "myVault": { - "message": "خزنتي" + "message": "خزانتي" }, "allVaults": { - "message": "جميع الخزنات" + "message": "جميع الخزانات" }, "vault": { - "message": "الخزنة" + "message": "الخزانة" }, "vaults": { - "message": "الخزنات" + "message": "الخزانات" }, "vaultItems": { - "message": "عناصر الخزنة" + "message": "عناصر الخزانة" }, "filter": { "message": "تصفية" @@ -970,7 +985,7 @@ "message": "لا" }, "loginOrCreateNewAccount": { - "message": "قم بتسجيل الدخول أو أنشئ حساباً جديداً لتتمكن من الوصول إلى خزنتك السرية." + "message": "قم بتسجيل الدخول أو أنشئ حساباً جديداً لتتمكن من الوصول إلى خزانتك السرية." }, "loginWithDevice": { "message": "تسجيل الدخول باستخدام جهاز" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "تسجيل الدخول باستخدام الجهاز يجب أن يتم إعداده في إعدادات تطبيق Bitwarden. هل تحتاج إلى خيار آخر؟" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "بَدْء تسجيل الدخول" }, @@ -1123,7 +1153,7 @@ "message": "كلمة المرور الرئيسية" }, "masterPassDesc": { - "message": "كلمة المرور الرئيسية هي كلمة المرور التي تستخدمها للوصول إلى خزنتك. من المهم جدا ألا تنسى كلمة المرور الرئيسية. لا توجد طريقة لاسترداد كلمة المرور في حال نسيتها." + "message": "كلمة المرور الرئيسية هي كلمة المرور التي تستخدمها للوصول إلى خزانتك. من المهم جدا ألا تنسى كلمة المرور الرئيسية. لا توجد طريقة لاسترداد كلمة المرور في حال نسيتها." }, "masterPassImportant": { "message": "لا يمكن استعادة كلمة المرور الرئيسية إذا نسيتها!" @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "تم إرسال إشعار إلى جهازك." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "الإصدار $VERSION_NUMBER$", "placeholders": { @@ -1485,7 +1521,7 @@ "message": "تحذير" }, "confirmVaultExport": { - "message": "تأكيد تصدير الخزنة" + "message": "تأكيد تصدير الخزانة" }, "confirmSecretsExport": { "message": "Confirm secrets export" @@ -1509,7 +1545,7 @@ "message": "Export from" }, "exportVault": { - "message": "تصدير الخزنة" + "message": "تصدير الخزانة" }, "exportSecrets": { "message": "Export secrets" @@ -1560,7 +1596,7 @@ "message": "This file is password-protected. Please enter the file password to import data." }, "exportSuccess": { - "message": "تم تصدير بيانات الخزنة الخاصة بك." + "message": "تم تصدير بيانات الخزانة الخاصة بك" }, "passwordGenerator": { "message": "مولّد كلمات المرور" @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "سجل كلمات المرور" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "مسح", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "الرجاء تسجيل الدخول مرة أخرى." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1762,7 +1822,7 @@ } }, "purgeVault": { - "message": "مسح الخزنة" + "message": "مسح الخزانة" }, "purgedOrganizationVault": { "message": "Purged organization vault." @@ -1925,7 +1985,7 @@ "message": "التفضيلات" }, "preferencesDesc": { - "message": "تخصيص تجرِبة خزنة الويب الخاصة بك." + "message": "تخصيص تجرِبة خزانة الويب الخاصة بك." }, "preferencesUpdated": { "message": "Preferences saved" @@ -1934,7 +1994,7 @@ "message": "اللّغة" }, "languageDesc": { - "message": "تغيير اللغة المستخدمة في خزنة الويب." + "message": "تغيير اللغة المستخدمة في خزانة الويب." }, "enableFavicon": { "message": "إظهار أيقونات الموقع" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "عرض جميع خيارات تسجيل الدخول" }, @@ -3714,6 +3780,15 @@ "device": { "message": "الجهاز" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4246,7 +4373,7 @@ "message": "عوامل التصفية" }, "vaultTimeout": { - "message": "مهلة الخزنة" + "message": "مهلة الخزانة" }, "vaultTimeout1": { "message": "Timeout" @@ -4379,7 +4506,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintMatchInfo": { - "message": "الرجاء التأكد من أن الخزنة الخاصة بك غير مقفلة وأن بصمة الإصبع تتطابق مع الجهاز الآخر." + "message": "الرجاء التأكد من أن الخزانة الخاصة بك غير مقفلة وأن بصمة الإصبع تتطابق مع الجهاز الآخر." }, "fingerprintPhraseHeader": { "message": "بصمة الإصبع" @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4509,13 +4639,13 @@ "message": "User preference" }, "vaultTimeoutAction": { - "message": "إجراء مهلة الخزنة" + "message": "إجراء مهلة الخزانة" }, "vaultTimeoutActionLockDesc": { - "message": "الخزنة المقفلة تتطلب إعادة إدخال كلمة المرور الرئيسية الخاصة بك للوصول إليها مرة أخرى." + "message": "الخزانة المقفلة تتطلب إعادة إدخال كلمة المرور الرئيسية الخاصة بك للوصول إليها مرة أخرى." }, "vaultTimeoutActionLogOutDesc": { - "message": "تسجيل الخروج من الخزنة يتطلب إعادة المصادقة للوصول إليها مرة أخرى." + "message": "تسجيل الخروج من الخزانة يتطلب إعادة المصادقة للوصول إليها مرة أخرى." }, "lock": { "message": "قفل", @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "قم بتسجيل الدخول بسرعة باستخدام بوابة تسجيل الدخول الأحادي لمؤسستك. الرجاء إدخال معرف الـSSO الخاص بمؤسستك للبدء." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "تسجيل الدخول الأُحادي للمؤسسات – SSO" }, @@ -5487,7 +5623,7 @@ "message": "المظهر" }, "themeDesc": { - "message": "اختر مظهر خزنة الويب خاصتك." + "message": "اختر مظهر خزانة الويب خاصتك." }, "themeSystem": { "message": "تلقائي" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "تم إرسال البريد الإلكتروني" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "rustSDKRepo": { - "message": "View Rust repository" + "eventManagement": { + "message": "Event management" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "deviceManagement": { + "message": "Device management" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "javaSDKRepo": { - "message": "View Java repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 10f948b7a5b..b140ae320be 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kritik tətbiqlər" }, + "accessIntelligence": { + "message": "Müraciət Kəşfiyyatı" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Risk Məlumatları" }, "passwordRisk": { "message": "Parol riski" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Tətbiqlər arasında riskli (zəif, ifşa olunmuş və ya təkrar istifadə olunmuş) parolları incələyin. İstifadəçilərinizin riskli parollara yönəlmiş təhlükəsizlik tədbirlərinə əhəmiyyət vermələri üçün kritik tətbiqlərinizi seçin." }, "dataLastUpdated": { "message": "Datanın son güncəlləmə tarixi: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Məlumatlandırılan üzvlər" }, + "revokeMembers": { + "message": "Üzvləri ləğv et" + }, + "restoreMembers": { + "message": "Üzvləri bərpa et" + }, + "cannotRestoreAccessError": { + "message": "Təşkilat müraciəti bərpa edilə bilmir" + }, "allApplicationsWithCount": { "message": "Bütün tətbiqlər ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Güvənli qeyd" }, + "typeSshKey": { + "message": "SSH açarı" + }, "typeLoginPlural": { "message": "Girişlər" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Cihazla giriş, Bitwarden tətbiqinin ayarlarında qurulmalıdır. Başqa bir seçimə ehtiyacınız var?" }, + "needAnotherOptionV1": { + "message": "Başqa bir seçimə ehtiyacınız var?" + }, "loginWithMasterPassword": { "message": "Ana parolla giriş et" }, @@ -994,7 +1012,7 @@ "message": "Keçid açarı ilə giriş et" }, "useSingleSignOn": { - "message": "Tək daxil olma üsulunu istifadə et" + "message": "Vahid daxil olma üsulunu istifadə et" }, "welcomeBack": { "message": "Yenidən xoş gəlmisiniz" @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Bitwarden-ə giriş edin" }, + "authenticationTimeout": { + "message": "Kimlik doğrulama vaxtı bitdi" + }, + "authenticationSessionTimedOut": { + "message": "Kimlik doğrulama seansının vaxtı bitdi. Lütfən giriş prosesini yenidən başladın." + }, "verifyIdentity": { "message": "Kimliyinizi doğrulayın" }, + "whatIsADevice": { + "message": "Cihaz nədir?" + }, + "aDeviceIs": { + "message": "Cihaz, giriş etdiyiniz Bitwarden tətbiqinin unikal quraşdırmasıdır. Yenidən quraşdırılması, tətbiq datasının təmizlənməsi və ya çərəzlərin təmizlənməsi, cihazın bir neçə dəfə görünməsinə səbəb ola bilər." + }, "logInInitiated": { "message": "Giriş etmə başladıldı" }, @@ -1270,7 +1300,7 @@ "message": "Bu kolleksiyadakı bütün elementlərə baxmaq üçün icazəniz yoxdur." }, "youDoNotHavePermissions": { - "message": "Bu kolleksiya ilə bağlı icazəniz yoxdur" + "message": "Bu kolleksiyaya icazəniz yoxdur" }, "noCollectionsInList": { "message": "Siyahılanacaq heç bir kolleksiya yoxdur." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Cihazınıza bir bildiriş göndərildi." }, + "aNotificationWasSentToYourDevice": { + "message": "Cihazınıza bir bildiriş göndərildi" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Hesabınızın kilidinin açıq olduğuna və barmaq izi ifadəsinin digər cihazda uyuşduğuna əmin olun" + }, "versionNumber": { "message": "Versiya $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Parol tarixçəsi" }, + "generatorHistory": { + "message": "Yaradıcı tarixçəsi" + }, + "clearGeneratorHistoryTitle": { + "message": "Yaradıcı tarixçəsini təmizlə" + }, + "cleargGeneratorHistoryDescription": { + "message": "Davam etsəniz, yaradıcı tarixçəsindəki bütün girişlər həmişəlik silinəcək. Davam etmək istədiyinizə əminsiniz?" + }, "noPasswordsInList": { "message": "Siyahılanacaq heç bir parol yoxdur." }, + "clearHistory": { + "message": "Tarixçəni təmizlə" + }, + "nothingToShow": { + "message": "Göstəriləcək heç nə yoxdur" + }, + "nothingGeneratedRecently": { + "message": "Təzəlikcə heç nə yaratmamısınız" + }, "clear": { "message": "Təmizlə", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Lütfən yenidən giriş edin." }, + "currentSession": { + "message": "Hazırkı seans" + }, + "requestPending": { + "message": "Tələb gözlənir" + }, "logBackInOthersToo": { "message": "Lütfən yenidən giriş edin. Digər Bitwarden tətbiqlərini istifadə edirsinizsə, onlardan da çıxış edib təkrar giriş etməlisiniz." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Bütün seansların səlahiyyəti götürüldü" }, - "accountIsManagedMessage": { - "message": "Bu hesab $ORGANIZATIONNAME$ tərəfindən idarə olunur", + "accountIsOwnedMessage": { + "message": "$ORGANIZATIONNAME$ bu hesabın sahibidir", "placeholders": { "organizationName": { "content": "$1", @@ -3123,7 +3183,7 @@ "message": "Siyasətlər" }, "singleSignOn": { - "message": "Tək daxil olma" + "message": "Vahid daxil olma" }, "editPolicy": { "message": "Siyasətə düzəliş et" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 dəvət haqqınız var." + }, "userUsingTwoStep": { "message": "Bu istifadəçinin hesabını qorumaq üçün iki addımlı giriş istifadə edilir." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Bütün giriş seçimlərinə bax" + }, "viewAllLoginOptions": { "message": "Bütün giriş etmə seçimlərinə bax" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Giriş statusu" + }, + "firstLogin": { + "message": "İlk giriş" + }, + "trusted": { + "message": "Güvənli" + }, "creatingAccountOn": { "message": "Hesab yaradılır" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Brauzeri güncəllə" }, + "generatingRiskInsights": { + "message": "Risk təhlilləriniz yaradılır..." + }, "updateBrowserDesc": { "message": "Dəstəklənməyən bir veb brauzer istifadə edirsiniz. Veb seyf düzgün işləməyə bilər." }, + "freeTrialEndPromptCount": { + "message": "Ödənişsiz sınaq müddətiniz $COUNT$ günə bitir.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, ödənişsiz sınaq müddətiniz $COUNT$ günə bitir.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, ödənişsiz sınaq müddətiniz sabah bitir.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Ödənişsiz sınaq müddətiniz sabah bitir." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, ödənişsiz sınaq müddətiniz bu gün bitir.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Ödənişsiz sınaq müddətiniz bu gün bitir." + }, + "clickHereToAddPaymentMethod": { + "message": "Ödəniş üsulu əlavə etmək üçün bura klikləyin." + }, "joinOrganization": { "message": "Təşkilata qoşul" }, @@ -4388,6 +4515,9 @@ "message": "Barmaq izi ifadəsini təkrar soruşma", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Tələbiniz təsdiqləndikdə bildiriş alacaqsınız" + }, "free": { "message": "Ödənişsiz", "description": "Free, as in 'Free beer'" @@ -4616,10 +4746,16 @@ "message": "Təşkilat identifikatoru" }, "ssoLogInWithOrgIdentifier": { - "message": "Təşkilatınızın tək daxil olma portalını istifadə edərək giriş edin. Başlatmaq üçün lütfən təşkilatınızın identifikatorunu daxil edin." + "message": "Təşkilatınızın vahid daxil olma portalını istifadə edərək giriş edin. Başlatmaq üçün lütfən təşkilatınızın SSO identifikatorunu daxil edin." + }, + "singleSignOnEnterOrgIdentifier": { + "message": "Başlamaq üçün təşkilatınızın SSO identifikatorunu daxil edin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "SSO provayderinizlə giriş etmək üçün başlamaq üzrə təşkilatınızın SSO identifikatorunu daxil edin. Yeni bir cihazdan giriş etdiyiniz zaman bu SSO identifikatorunu daxil etməyiniz lazım gələ bilər." }, "enterpriseSingleSignOn": { - "message": "Müəssisə üçün tək daxil olma" + "message": "Müəssisə üçün vahid daxil olma" }, "ssoHandOff": { "message": "Bu vərəqi indi bağlayıb uzantıda davam edə bilərsiniz." @@ -4680,7 +4816,7 @@ "message": "SSO ilə əlaqələndir" }, "singleOrg": { - "message": "Tək təşkilat" + "message": "Vahid təşkilat" }, "singleOrgDesc": { "message": "İstifadəçilərin digər təşkilatlara qoşulmasını məhdudlaşdırın." @@ -4698,19 +4834,19 @@ "message": "Uyumlu olmayan üzvlər, digər bütün təşkilatları tərk edənə qədər rədd edilmiş statusa daxil ediləcək. Administratorlar azaddır və uyum təmin edildikdən sonra üzvləri bərpa edə bilər." }, "requireSso": { - "message": "Tək daxil olma kimlik doğrulaması" + "message": "Vahid daxil olma kimlik doğrulamasını tələb et" }, "requireSsoPolicyDesc": { - "message": "İstifadəçilərin müəssisə kimi \"tək daxil olma\" metodu ilə giriş etməsini məcburi edin." + "message": "Üzvlərin \"Müəssisə üçün vahid daxil olma\" üsulu ilə giriş etməsini tələb et." }, "prerequisite": { "message": "Ön şərt" }, "requireSsoPolicyReq": { - "message": "\"Tək təşkilat\" müəssisə siyasəti, bu siyasəti aktivləşdirməzdən əvvəl fəallaşdırılmalıdır." + "message": "Bu siyasəti aktivləşdirməzdən əvvəl vahid təşkilat üçün Müəssisə siyasəti işə salınmalıdır." }, "requireSsoPolicyReqError": { - "message": "Tək təşkilat siyasəti fəal deyil." + "message": "Vahid təşkilat siyasəti qurulmayıb." }, "requireSsoExemption": { "message": "Təşkilat sahibləri və administratorlar, bu siyasətin tətbiq edilməsindən azaddırlar." @@ -5433,7 +5569,7 @@ "message": "Ana parolları olan mövcud hesablar, administratorların öz hesablarını geri qaytara bilməsi üçün üzvlərin öz-özünə yazılmalarını tələb edəcək. Avto-yazılma, yeni üzvlər üçün hesabın geri qaytarılmasını işə salacaq." }, "accountRecoverySingleOrgRequirementDesc": { - "message": "Bu siyasət aktivləşdirilməzdən əvvəl, \"Tək təşkilat\" Müəssisə siyasəti işə salınmalıdır." + "message": "Bu siyasəti aktivləşdirməzdən əvvəl vahid təşkilat üçün Müəssisə siyasəti işə salınmalıdır." }, "resetPasswordPolicyAutoEnroll": { "message": "Avtomatik qeydiyyat" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "İstisna edildi, bu əməliyyat üçün etibarlı deyil." }, + "nonCompliantMembersTitle": { + "message": "Uyğun olmayan üzvlər" + }, + "nonCompliantMembersError": { + "message": "Vahid təşkilat və ya İki addımlı giriş siyasəti ilə uyumlu olmayan üzvlər, siyasət tələblərinə əməl edənə qədər bərpa oluna bilməz." + }, "fingerprint": { "message": "Barmaq izi" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "İdarə edilən istifadəçilərə həm də \"Hesab geri qaytarılmasını idarə et\" icazəsi verilməlidir" }, @@ -5898,10 +6054,10 @@ "message": "Bağlanma növü" }, "idpSingleSignOnServiceUrl": { - "message": "Tək daxil olma xidmətinin URL-si" + "message": "Vahid daxil olma xidmətinin URL-si" }, "idpSingleLogoutServiceUrl": { - "message": "Tək çıxış etmə xidmətinin URL-si" + "message": "Vahid çıxış etmə xidmətinin URL-si" }, "idpX509PublicCert": { "message": "X509 İctimai Sertifikat" @@ -5919,7 +6075,7 @@ "message": "Kimlik doğrulama tələblərini imzala" }, "ssoSettingsSaved": { - "message": "Tək daxil olma konfiqurasiyası saxlanıldı." + "message": "Vahid daxil olma konfiqurasiyası saxlanıldı" }, "sponsoredFamilies": { "message": "Ödənişsiz Bitwarden Ailələri" @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-poçt göndərildi" }, - "revokeSponsorshipConfirmation": { - "message": "Bu hesabı sildikdən sonra bu abunəliyə və əlaqəli fakturalara görə Ailələr təşkilatının sahibi məsuliyyət daşıyacaq. Davam etmək istəyirsiniz?" - }, "removeSponsorshipSuccess": { "message": "Sponsorluq silindi" }, @@ -6094,7 +6247,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpAnchor": { - "message": "tək daxil olma kimlik doğrulama siyasətini tələb et", + "message": "vahid daxil olma kimlik doğrulama siyasətini tələb et", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpEnd": { @@ -6115,7 +6268,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { - "message": "SSO kimlik doğrulaması və tək təşkilat siyasətləri tələb olunur", + "message": "SSO kimlik doğrulaması və vahid təşkilat siyasətləri tələbi", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Varlıq Kimliyi bir URL deyilsə tələb olunur." }, + "offerNoLongerValid": { + "message": "Bu təklif artıq yararlı deyil. Daha çox məlumat üçün təşkilatınızın inzibatçısı ilə əlaqə saxlayın." + }, "openIdOptionalCustomizations": { "message": "İxtiyari Özəlləşdirmələr" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "E-poçt yarat" }, - "generatorBoundariesHint": { - "message": "Dəyər $MIN$-$MAX$ arasında olmalıdır", + "spinboxBoundariesHint": { + "message": "Dəyər, $MIN$-$MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Güclü bir parol yaratmaq üçün $RECOMMENDED$ və ya daha çox xarakter istifadə edin.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Güclü bir keçid ifadəsi yaratmaq üçün $RECOMMENDED$ və ya daha çox söz istifadə edin.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "İstifadəçi adı növü" }, @@ -6681,6 +6857,10 @@ "message": "SCIM təmin etmə vasitəsilə tərcih etdiyiniz kimlik doğrulama provayderləri ilə istifadəçiləri və qrupları avtomatik təmin edin", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "SCIM təmin etmə vasitəsilə tərcih etdiyiniz kimlik doğrulama provayderləri ilə istifadəçiləri və qrupları avtomatik təmin edin. Dəstəklənən inteqrasiyaları tapın", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM-i fəallaşdır", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Giriş başladıldı" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Gələcək girişləri problemsiz etmək üçün bu cihazı xatırla" + }, "deviceApprovalRequired": { "message": "Cihaz təsdiqi tələb olunur. Aşağıdan bir təsdiq variantı seçin:" }, + "deviceApprovalRequiredV2": { + "message": "Cihaz təsdiqi tələb olunur" + }, + "selectAnApprovalOptionBelow": { + "message": "Aşağıdan bir təsdiq seçimi edin" + }, "rememberThisDevice": { "message": "Bu cihazı xatırla" }, @@ -7987,7 +8176,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "tək təşkilat", + "message": "vahid təşkilat", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartTwo": { @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Tələbi təsdiqlə" }, + "deviceApproved": { + "message": "Cihaz təsdiqləndi" + }, + "deviceRemoved": { + "message": "Cihaz silindi" + }, + "removeDevice": { + "message": "Cihazı sil" + }, + "removeDeviceConfirmation": { + "message": "Bu cihazı silmək istədiyinizə əminsiniz?" + }, "noDeviceRequests": { "message": "Heç bir cihaz tələbi yoxdur" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "İstifadəçi e-poçtu əskikdir" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiv istifadəçi e-poçtu tapılmadı. Hesabınızdan çıxış edilir." + }, "deviceTrusted": { "message": "Cihaz güvənlidir" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Təşkilat üçün kolleksiya davranışını idarə et" }, - "limitCollectionCreationDeletionDesc": { - "message": "Kolleksiya yaradılmasını və silinməsini sahibləri və adminləri ilə məhdudlaşdır" - }, "limitCollectionCreationDesc": { "message": "Kolleksiya yaradılmasını sahibləri və adminləri ilə məhdudlaşdır" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "bir ödəniş üsulu əlavə edin", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Təşkilat məlumatları" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Öz tətbiqlərinizi qurmaq üçün \"Bitwarden Sirr Meneceri SDK\"sını aşağıdakı proqramlaşdırma dillərində istifadə edin." }, - "setUpGithubActions": { - "message": "Github Actions qur" + "ssoDescStart": { + "message": "Konfiqurasiya et", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "Kimlik Provayderiniz üçün icra bələdçisini istifadə edərək Bitwarden üçün", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "İstifadəçi təminatı" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Konfiqurasiya et", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Kubernetes-i qur" + "scimIntegrationDescEnd": { + "message": "Kimlik Provayderinizin icra bələdçisini istifadə edərək istifadəçiləri və qrupları Bitwarden-ə avtomatik olaraq təmin etmək üçün (System for Cross-domain Identity Management).", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "GitLab CI/CD qur" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Ansible qur" + "bwdcDesc": { + "message": "Kimlik Provayderinizin icra bələdçisini istifadə edərək istifadəçiləri və qrupları avtomatik olaraq təmin etmək üçün Bitwarden Directory Connector-u konfiqurasiya edin." }, - "rustSDKRepo": { - "message": "Rust repozitoriyasına bax" + "eventManagement": { + "message": "Event idarəetməsi" }, - "cSharpSDKRepo": { - "message": "C# repozitoriyasına bax" + "eventManagementDesc": { + "message": "Platformanız üçün icra bələdçisini istifadə edərək Bitwarden event log-larını SIEM sisteminizə inteqrasiya edin." }, - "cPlusPlusSDKRepo": { - "message": "C++ repozitoriyasına bax" + "deviceManagement": { + "message": "Cihaz idarəetməsi" }, - "jsWebAssemblySDKRepo": { - "message": "JS WebAssembly repozitoriyasına bax" + "deviceManagementDesc": { + "message": "Platformanız üçün icra bələdçisini istifadə edərək Bitwarden üçün cihaz idarəetməsini konfiqurasiya edin." }, - "javaSDKRepo": { - "message": "Java repozitoriyasına bax" + "integrationCardTooltip": { + "message": "$INTEGRATION$ icra bələdçisini başlat.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "$INTEGRATION$ qur.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Python repozitoriyasına bax" + "smSdkTooltip": { + "message": "$SDK$ repozitoriyasına bax", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "php repozitoriyasına bax" + "integrationCardAriaLabel": { + "message": "$INTEGRATION$ icra bələdçisini yeni bir vərəqdə aç.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Ruby repozitoriyasına bax" + "smSdkAriaLabel": { + "message": "$SDK$ repozitoriyasına yeni vərəqdə bax.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Go repozitoriyasına bax" + "smIntegrationCardAriaLabel": { + "message": "$INTEGRATION$ icra bələdçisini yeni bir vərəqdə qur.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Provayder kimi idarə etmək üçün yeni bir client təşkilatı yaradın. Əlavə yerlər növbəti faktura dövründə əks olunacaq." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "İdarə edilən Xidmət Provayderi" }, + "managedServiceProvider": { + "message": "İdarə edilən xidmət provayderi" + }, + "multiOrganizationEnterprise": { + "message": "Çoxlu təşkilat müəssisəsi" + }, "orgSeats": { "message": "Təşkilat yerləri" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Güncəlllənən vergi məlumatı" }, + "billingInvalidTaxIdError": { + "message": "Yararsız vergi kimliyi, bunun xəta olduğunu düşünürsünüzsə, dəstək komandası ilə əlaqə saxlayın." + }, + "billingTaxIdTypeInferenceError": { + "message": "Vergi kimliyi nömrənizi doğrulaya bilmədik, bunun xəta olduğunu düşünürsünüzsə, dəstək komandası ilə əlaqə saxlayın." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Yararsız vergi kimliyi, bunun xəta olduğunu düşünürsünüzsə, dəstək komandası ilə əlaqə saxlayın." + }, + "billingPreviewInvoiceError": { + "message": "Faktura önizləməsi zamanı bir xəta baş verdi. Lütfən daha sonra yenidən sınayın." + }, "unverified": { "message": "Doğrulanmayıb" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB əlavə saxlama sahəsi" }, + "sshKeyAlgorithm": { + "message": "Açar alqoritmi" + }, + "sshKeyFingerprint": { + "message": "Barmaq izi" + }, + "sshKeyPrivateKey": { + "message": "Private açar" + }, + "sshKeyPublicKey": { + "message": "Public açar" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium hesab" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Bir domeni doğrulamaq, tək təşkilat siyasətini işə salacaq." + "claim-domain-single-org-warning": { + "message": "Bir domeni götürmək, vahid təşkilat siyasətini işə salacaq." }, "single-org-revoked-user-warning": { "message": "Uyumlu olmayan üzvlər rədd ediləcək. Digər bütün təşkilatları tərk etdikdən sonra üzvlər, administratorlar tərəfindən bərpa edilə bilər." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Bir hesab silindikdə, Bitwarden hesabı və onun fərdi seyf dataları həmişəlik silinir. Təşkilatdakı kolleksiya dataları qalır. Bunları yenidən fəallaşdırmaq üçün bir hesab yaradılmalı və yenidən təşkilata qoşulması lazımdır.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Bu, sahibi $NAME$ olan bütün elementləri həmişəlik siləcək. Kolleksiya elementləri təsirlənmir.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Bu, sahibi aşağıdakı üzvlər olan bütün elementləri həmişəlik siləcək. Kolleksiya elementləri təsirlənmir.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "$NAME$ silindi", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "İstifadəçi təşkilatdan çıxarıldı və əlaqələndirilmiş bütün istifadəçi dataları silindi." + }, + "deletedUserId": { + "message": "$ID$ istifadəçisi silindi - bir sahib/admin, istifadəçi hesabını sildi", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "$ID$ istifadəçisi təşkilatı tərk etdi", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ fəaliyyəti dayandırıldı", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." + }, + "suspendedOwnerOrgMessage": { + "message": "Təşkilatınıza yenidən müraciət qazanmaq üçün bir ödəniş üsulu əlavə edin." + }, + "deleteMembers": { + "message": "Üzvləri sil" + }, + "noSelectedMembersApplicable": { + "message": "Bu əməliyyat, seçilmiş üzvlərin heç birinə aid deyil." + }, + "deletedSuccessfully": { + "message": "Uğurla silindi" + }, + "freeFamiliesSponsorship": { + "message": "Ödənişsiz Bitwarden Families sponsorluğunu sil" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Üzvlərin bu təşkilat vasitəsilə Families planını istifadə etməsinə icazə verməyin." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Bank hesabı ilə ödəniş, yalnız Amerika Birləşmiş Ştatlarındakı müştərilər üçün əlçatandır. Bank hesabınızı doğrulamağınız tələb olunacaq. Növbəti 1-2 iş günü ərzində mikro depozit qoyacağıq. Bank hesabını yoxlamaq üçün bu depozitdəki əməliyyat açıqlayıcı kodunu təşkilatın faktura səhifəsində daxil edin. Bank hesabı doğrulanmadıqda ödəniş buraxılacaq və abunəliyiniz dayandırılacaq." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Bank hesabınıza mikro depozit qoymuşuq (bu, 1-2 iş günü çəkə bilər). Depozit açıqlamasındakı 'SM' ilə başlayan altı rəqəmli kodu daxil edin. Bank hesabı doğrulanmadıqda ödəniş buraxılacaq və abunəliyiniz dayandırılacaq." + }, + "descriptorCode": { + "message": "Açıqlayıcı kod" + }, + "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" + }, + "removeMembers": { + "message": "Üzvləri çıxart" + }, + "devices": { + "message": "Cihazlar" + }, + "deviceListDescription": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib. Tanımadığınız cihaz varsa, onu silin." + }, + "deviceListDescriptionTemp": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib." + }, + "claimedDomains": { + "message": "Götürülmüş domenlər" + }, + "claimDomain": { + "message": "Domen götür" + }, + "reclaimDomain": { + "message": "Domeni təkrar götür" + }, + "claimDomainNameInputHint": { + "message": "Nümunə: mydomain.com. Alt domenlərin götürülməsi üçün ayrıca girişlər tələb olunur." + }, + "automaticClaimedDomains": { + "message": "Avtomatik götürülən domenlər" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden, ilk 72 saat ərzində domeni 3 dəfə götürməyə çalışacaq. Əgər domen götürülə bilməsə, \"host\"unuzdakı DNS qeydini yoxlayın və manual götürün. Domen götürülməsə, 7 gün ərzində təşkilatınızdan silinəcək." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ götürülmədi. DNS qeydlərinizi yoxlayın.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Götürüldü" + }, + "domainStatusUnderVerification": { + "message": "Doğrulama altında" + }, + "claimedDomainsDesc": { + "message": "Domenlə uyuşan e-poçt ünvanına sahib bütün üzvlərin hesablarına sahib olmaq üçün bir domen götürün. Üzvlər giriş edərkən SSO identifikatorunu ötürə biləcək. Həmçinin inzibatçılar, üzv hesablarını silə biləcək." + }, + "invalidDomainNameClaimMessage": { + "message": "Giriş, yararlı bir format deyil. Format: mydomain.com. Alt domenlərin götürülməsi üçün ayrıca girişlər tələb olunur." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ götürüldü", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ götürülmədi", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "$EMAIL$ silinsə, bu Ailələr planı üçün sponsorluq istifadə edilə bilməz. Davam etmək istədiyinizə əminsiniz?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "$EMAIL$ silinsə, bu Ailə planı üçün sponsorluq bitəcək və saxlanılmış ödəniş üsulundan $DATE$ tarixində $40 + müvafiq vergi tutulacaq. $DATE$ tarixinə qədər yeni bir sponsorluq istifadə edə bilməyəcəksiniz. Davam etmək istədiyinizə əminsiniz?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domen götürüldü" + }, + "organizationNameMaxLength": { + "message": "Təşkilat adı 50 xarakterdən çox ola bilməz." + }, + "resellerRenewalWarning": { + "message": "Abunəliyiniz tezliklə yenilənəcək. Kəsintisiz xidməti təmin etmək və yeniləməni $RENEWAL_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Abunəliyinizə aid faktura $ISSUED_DATE$ tarixində təqdim edildi. Kəsintisiz xidməti təmin etmək və yeniləməni $DUE_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Abunəliyinizə aid faktura üzrə ödəniş edilmədi. Kəsintisiz xidməti təmin etmək və yeniləməni $GRACE_PERIOD_END$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Təşkilat abunəliyi yenidən başladıldı" + }, + "restartSubscription": { + "message": "Abunəliyinizi yenidən başladın" + }, + "suspendedManagedOrgMessage": { + "message": "Kömək üçün $PROVIDER$ ilə əlaqə saxlayın.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 6268fe9c1db..2a037c5b11c 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Абароненая нататка" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Лагіны" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Неабходна наладзіць уваход з прыладай у наладах мабільнай праграмы Bitwarden. Патрабуецца іншы варыянт?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Увайсці з асноўным паролем" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Ініцыяваны ўваход" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Апавяшчэнне было адпраўлена на вашу прыладу." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Версія $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Гісторыя пароляў" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "У спісе адсутнічаюць паролі." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Ачысціць", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Калі ласка, увайдзіце паўторна." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Калі ласка, увайдзіце паўторна. Калі вы выкарыстоўваеце іншыя праграмы Bitwarden, выйдзіце з іх, а потым увайдзіце яшчэ раз." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Аўтарызацыя ўсіх сеансаў скасавана" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Гэты карыстальнік выкарыстоўвае двухэтапны ўваход для абароны свайго ўліковага запісу." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Паглядзець усе варыянты ўваходу" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Прылада" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Абнавіць браўзер" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Ваш браўзер не падтрымліваецца. Вэб-сховішча можа працаваць няправільна." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Далучыцца да арганізацыі" }, @@ -4388,6 +4515,9 @@ "message": "Ніколі не пытаць пра праверку фразы адбітку пальца для запрошаных карыстальнікаў (не рэкамендуецца)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Бясплатна", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Уваходзьце з выкарыстаннем партала адзінага ўваходу вашай арганізацыі. Калі ласка, увядзіце ідэнтыфікатар вашай арганізацыі для пачатку працы." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Адзіны ўваход прадпрыемства (SSO)" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Выключэнне нельга ўжыць для гэтага дзеяння." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Адбітак" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для кіравання карыстальнікамі неабходна даць дазвол на кіраванне аднаўленнем уліковым запісам" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Ліст адпраўлены" }, - "revokeSponsorshipConfirmation": { - "message": "Пасля выдалення гэтага ўліковага запісу, спонсарства тарыфнага плана Bitwarden Families завяршыцца ў канцы плацежнага перыяду. У вас не будзе магчымасці скарыстацца новай спонсарскай прапановай, пакуль не завяршыцца тэрмін бягучай прапановы. Вы сапраўды хочаце працягнуць?" - }, "removeSponsorshipSuccess": { "message": "Спансіраванне выдалена" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Патрабуецца, калі ідэнтыфікатар аб'екта не з'яўляецца URL-адрасам." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Дадатковыя дапасаванні" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тып імя карыстальніка" }, @@ -6681,6 +6857,10 @@ "message": "Аўтаматычна забяспечваць карыстальнікаў і групы пажаданымі пасведчаннямі пастаўшчыка праз SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Уключыць SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Ініцыяваны ўваход" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Патрабуецца ўхваленне прылады. Выберыце параметры ўхвалення ніжэй:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Запомніць гэту прыладу" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Ухваліць запыт" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Няма запытаў ад прылады" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Адсутнічае электронная пошта карыстальніка" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Давераная прылада" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index fd236374cb9..531cfd18a42 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Важни приложения" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Подробности за рисковете" }, "passwordRisk": { "message": "Рискова парола" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Последно обновяване на данните: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Известени членове" }, + "revokeMembers": { + "message": "Отнемане на достъпа на членове" + }, + "restoreMembers": { + "message": "Възстановяване на достъпа на членове" + }, + "cannotRestoreAccessError": { + "message": "Достъпът до организацията не може да бъде възстановен" + }, "allApplicationsWithCount": { "message": "Всички приложения ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Защитена бележка" }, + "typeSshKey": { + "message": "SSH ключ" + }, "typeLoginPlural": { "message": "Записи" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Вписването с устройство трябва да е включено в настройките на приложението на Битуорден. Друга настройка ли търсите?" }, + "needAnotherOptionV1": { + "message": "Предпочитате друг вариант?" + }, "loginWithMasterPassword": { "message": "Вписване с главната парола" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Впишете се в Битуорден" }, + "authenticationTimeout": { + "message": "Време на давност за удостоверяването" + }, + "authenticationSessionTimedOut": { + "message": "Сесията за удостоверяване е изтекла. Моля, започнете отначало процеса по вписване." + }, "verifyIdentity": { "message": "Потвърдете самоличността си" }, + "whatIsADevice": { + "message": "Какво представлява едно устройство?" + }, + "aDeviceIs": { + "message": "Едно устройство наричаме инсталацията на приложението Битуорден, където сте се вписали. Ако преинсталирате приложението, изчистите данните му или изтриете бисквитките си, това устройство може да се появи няколко пъти в списъка." + }, "logInInitiated": { "message": "Вписването е стартирано" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Към устройството Ви е изпратено известие." }, + "aNotificationWasSentToYourDevice": { + "message": "Към устройството Ви е изпратено известие" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Уверете се, че регистрацията Ви е отключена и че уникалната фраза съвпада с другото устройство" + }, "versionNumber": { "message": "Версия $VERSION_NUMBER$", "placeholders": { @@ -1441,7 +1477,7 @@ "message": "Наистина ли искате да продължите?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Изберете папка, в която да се добавят $COUNT$ избран(и) запис(а).", "placeholders": { "count": { "content": "$1", @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Хронология на паролата" }, + "generatorHistory": { + "message": "История на генерирането" + }, + "clearGeneratorHistoryTitle": { + "message": "Изчистване на историята на генериране" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ако продължите, всички записи в историята на генериране ще бъдат изтрити завинаги. Наистина ли искате това?" + }, "noPasswordsInList": { "message": "Няма пароли за показване." }, + "clearHistory": { + "message": "Изчистване на историята" + }, + "nothingToShow": { + "message": "Няма нищо за показване" + }, + "nothingGeneratedRecently": { + "message": "Скоро не сте генерирали нищо" + }, "clear": { "message": "Изчистване", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Впишете се отново." }, + "currentSession": { + "message": "Текуща сесия" + }, + "requestPending": { + "message": "Чакаща заявка" + }, "logBackInOthersToo": { "message": "Впишете се отново. Ако използвате и други приложения на Битуорден, впишете се отново и в тях." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Всички сесии са прекратени" }, - "accountIsManagedMessage": { - "message": "Тази регистрация се управлява от $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Тази регистрация е притежание на $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1869,7 +1929,7 @@ "message": "Грешка при дешифрирането на изнесения файл. Ключът за шифриране не отговаря на този, който е използван за изнасянето на данните." }, "destination": { - "message": "Destination" + "message": "Дестинация" }, "learnAboutImportOptions": { "message": "Научете повече относно възможностите за внасяне" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Имате 1 оставаща покана." + }, "userUsingTwoStep": { "message": "Този потребител използва двустепенна защита за достъп." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Вижте всички възможности за вписване" + }, "viewAllLoginOptions": { "message": "Вижте всички възможности за вписване" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Състояние на вписването" + }, + "firstLogin": { + "message": "Първо вписване" + }, + "trusted": { + "message": "Доверено" + }, "creatingAccountOn": { "message": "Създаване на регистрация в" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Обновяване на браузъра" }, + "generatingRiskInsights": { + "message": "Създаване на Вашата информация относно рисковете…" + }, "updateBrowserDesc": { "message": "Ползвате неподдържан браузър. Трезорът по уеб може да не сработи правилно." }, + "freeTrialEndPromptCount": { + "message": "Вашият безплатен пробен период приключва след $COUNT$ дни.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, Вашият безплатен пробен период приключва след $COUNT$ дни.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, Вашият безплатен пробен период приключва утре.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Вашият безплатен пробен период приключва утре." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, Вашият безплатен пробен период приключва днес.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Вашият безплатен пробен период приключва днес." + }, + "clickHereToAddPaymentMethod": { + "message": "Натиснете тук, за да добавите средство за разплащане." + }, "joinOrganization": { "message": "Присъединяване към организация" }, @@ -4388,6 +4515,9 @@ "message": "Без повторно питане за уникалната фраза", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Ще получите уведомление когато заявката бъде одобрена" + }, "free": { "message": "Безплатно", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Вписване чрез портала на организацията ви за еднократна идентификация. За да продължите, въведете идентификатора на организацията." }, + "singleSignOnEnterOrgIdentifier": { + "message": "За да започнете, въведете идентификатора за еднократно удостоверяване на организацията си" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "За да се впишете със своя доставчик на еднократно удостоверяване, въведете идентификатора за еднократно удостоверяване на организацията си. Може да се наложи да въвеждате този идентификатор когато се вписвате на ново устройство." + }, "enterpriseSingleSignOn": { "message": "Еднократна идентификация (SSO)" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Изключени като неподходящи за това действие." }, + "nonCompliantMembersTitle": { + "message": "Неизпълняващи изискванията членове" + }, + "nonCompliantMembersError": { + "message": "Членове, които не отговарят на изискването за Единствена организация или за Двустепенно удостоверяване не могат да бъдат възстановени, докато не изпълнят тези изисквания" + }, "fingerprint": { "message": "Отпечатък" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Управлението на потребителите трябва да бъде дадено заедно с разрешението за Управление на възстановяването на регистрации" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Писмото е изпратено" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Спонсорството е премахнато" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "Това предложение е вече не важи. Свържете се с администраторите на организацията си за повече информация." + }, "openIdOptionalCustomizations": { "message": "Незадължителни персонализации" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Генериране на електронна поща" }, - "generatorBoundariesHint": { - "message": "Стойността трябва да бъде между $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Стойността трябва да бъде между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип потребителско име" }, @@ -6681,6 +6857,10 @@ "message": "Предоставете на потребителите и групите си автоматично удостоверяване със своя предпочитан доставчик на удостоверителни данни като използвате удостоверяване чрез SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Предоставете на потребителите и групите си автоматично удостоверяване със своя предпочитан доставчик на удостоверителни данни като използвате удостоверяване чрез SCIM. Разгледайте поддържаните такива.", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Включване на SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Вписването е стартирано" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запомняне на това устройство, така че в бъдеще вписването да бъде по-лесно" + }, "deviceApprovalRequired": { "message": "Изисква се одобрение на устройството. Изберете начин за одобрение по-долу:" }, + "deviceApprovalRequiredV2": { + "message": "Необходимо е одобрение на устройството" + }, + "selectAnApprovalOptionBelow": { + "message": "Изберете начин за одобряване по-долу" + }, "rememberThisDevice": { "message": "Запомняне на това устройство" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Одобряване на заявката" }, + "deviceApproved": { + "message": "Устройството е одобрено" + }, + "deviceRemoved": { + "message": "Устройството е премахнато" + }, + "removeDevice": { + "message": "Премахване на устройството" + }, + "removeDeviceConfirmation": { + "message": "Наистина ли искате да премахнете това устройство?" + }, "noDeviceRequests": { "message": "Няма заявки от устройства" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Липсва е-поща на потребителя" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Не е намерена е-поща на активен потребител. Ще бъдете отписан(а)." + }, "deviceTrusted": { "message": "Устройството е доверено" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Управление на поведението на колекциите за организацията" }, - "limitCollectionCreationDeletionDesc": { - "message": "Ограничаване на създаването и изтриването на колекции, така че да може да се извършва само от собствениците и администраторите" - }, "limitCollectionCreationDesc": { "message": "Ограничаване на създаването на колекции, така че да може да се извършва само от собствениците и администраторите" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "добавете разплащателен метод", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Информация за организацията" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Използвайте набора за разработка (SDK) за Управлението на тайни на Битуорден със следните програмни езици, за да създадете свои собствени приложения." }, - "setUpGithubActions": { - "message": "Настройка на действия в Github" + "ssoDescStart": { + "message": "Настройте", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "за Битуорден, използвайки ръководството за внедряване на своя доставчик на удостоверителни данни.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Удостоверяване на потребителите" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Настройка на Kubernetes" + "scimIntegrationDescStart": { + "message": "Настройте ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Настройка на GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(система за управление на идентичностите между различни домейни), за да удостоверявате автоматично потребителите и групите в Битуорден чрез ръководството за внедряване на Вашия доставчик на удостоверителни данни.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Настройка на Ansible" + "bwdc": { + "message": "Свързване на директории в Битуорден" }, - "rustSDKRepo": { - "message": "Преглед на хранилището за Rust" + "bwdcDesc": { + "message": "Настройте Свързването на директории в Битуорден, за да удостоверявате автоматично потребителите и групите в Битуорден чрез ръководството за внедряване на Вашия доставчик на удостоверителни данни." }, - "cSharpSDKRepo": { - "message": "Преглед на хранилището за C#" + "eventManagement": { + "message": "Управление на събития" }, - "cPlusPlusSDKRepo": { - "message": "Преглед на хранилището за C++" + "eventManagementDesc": { + "message": "Внедрете журналите на събитията на Битуорден в своята система SIEM (за управление на системна информация и събития), като използвате ръководството за внедряване за платформата си." }, - "jsWebAssemblySDKRepo": { - "message": "Преглед на хранилището за JS WebAssembly" + "deviceManagement": { + "message": "Управление на устройства" }, - "javaSDKRepo": { - "message": "Преглед на хранилището за Java" + "deviceManagementDesc": { + "message": "Настройте управлението на устройства в Битуорден, като използвате ръководството за внедряване за платформата си." + }, + "integrationCardTooltip": { + "message": "Стартиране на ръководството за внедряване за $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Настройка на $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Преглед на хранилището за Python" + "smSdkTooltip": { + "message": "Преглед на хранилището на $SDK$", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Преглед на хранилището за php" + "integrationCardAriaLabel": { + "message": "отваряне на ръководството за внедряване на $INTEGRATION$ в нов раздел.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Преглед на хранилището за Ruby" + "smSdkAriaLabel": { + "message": "преглед на хранилището на $SDK$ в нов раздел.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Преглед на хранилището за Go" + "smIntegrationCardAriaLabel": { + "message": "настройване на ръководството за внедряване на $INTEGRATION$ в нов раздел.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Създайте нова организация, която да управлявате като доставчик. Допълнителните места ще бъдат отразени в следващия платежен период." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Управляван доставчик на услуга" + }, + "multiOrganizationEnterprise": { + "message": "Компания с множество организации" + }, "orgSeats": { "message": "Места в организацията" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Обновена данъчна информация" }, + "billingInvalidTaxIdError": { + "message": "Неправилен данъчен идентификатор. Ако смятате, че това е грешка, свържете се с поддръжката." + }, + "billingTaxIdTypeInferenceError": { + "message": "Не успяхме да потвърдим Вашия данъчен идентификатор. Ако смятате, че това е грешка, свържете се с поддръжката." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Неправилен данъчен идентификатор. Ако смятате, че това е грешка, свържете се с поддръжката." + }, + "billingPreviewInvoiceError": { + "message": "Възникна грешка при преглеждането на фактурата. Опитайте отново по-късно." + }, "unverified": { "message": "Непотвърден" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB допълнително място за съхранение" }, + "sshKeyAlgorithm": { + "message": "Алгоритъм на ключа" + }, + "sshKeyFingerprint": { + "message": "Отпечатък" + }, + "sshKeyPrivateKey": { + "message": "Частен ключ" + }, + "sshKeyPublicKey": { + "message": "Публичен ключ" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 бита" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 бита" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 бита" + }, "premiumAccounts": { "message": "6 платени регистрации" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Собствен хостинг" }, - "verified-domain-single-org-warning": { - "message": "Проверката на домейн ще включи и политиката за единствена организация." + "claim-domain-single-org-warning": { + "message": "Присвояването на домейн ще включи и политиката за единствена организация." }, "single-org-revoked-user-warning": { "message": "На членовете, които не отговарят на това условие, ще бъдат отнети правомощията. Администраторите могат да възстановяват правомощията на членовете, след като те напуснат всички останали организации." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Когато даден член бъде изтрит, неговата регистрация в Битуорден, както и данните от трезора му, ще бъдат изтрити завинаги. Данните за колекции ще останат в организацията. Ако искате да го върнете, той трябва да си създаде нова регистрация и да бъде включен отново.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Това действие ще изтрие завинаги всички елементи, притежавани от $NAME$. Елементите в колекции няма да бъдат засегнати.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Това действие ще изтрие завинаги всички елементи, притежавани от следните членове. Елементите в колекции няма да бъдат засегнати.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Изтрито: $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Потребителят беше премахнат от организацията и всичките му данни бяха изтрити." + }, + "deletedUserId": { + "message": "Изтрит потребител $ID$ – собственик / администратор е изтрил регистрацията на потребителя", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Потребител $ID$ напусна организацията", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "Достъпът на $ORGANIZATION$ е преустановен", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Свържете се със собственика на организацията си за съдействие." + }, + "suspendedOwnerOrgMessage": { + "message": "Ако искате отново да получите достъп до организацията си, добавете разплащателен метод." + }, + "deleteMembers": { + "message": "Изтриване на членове" + }, + "noSelectedMembersApplicable": { + "message": "Това действие не може да се изпълни за никого от избраните членове." + }, + "deletedSuccessfully": { + "message": "Успешно изтриване" + }, + "freeFamiliesSponsorship": { + "message": "Премахване на безплатното спонсориране на Битуорден за семейства" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Да не се разрешава на членовете да се възползват от Семейния план чрез тази организация." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Плащането чрез банкова сметка е налично само за клиенти от САЩ. Ще трябва да потвърдите банковата си сметка. В следващите 1-2 работни дни ще направим малък депозит. Въведете кода от описанието на трансакцията в страницата за плащанията на организацията, за да потвърдите банковата сметка. Ако не потвърдите банковата сметка, може да пропуснете плащането и абонаментът Ви да бъде спрян." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Направихме малък депозит по банковата Ви сметка (може да отнеме 1-2 дни да го видите). Въведете шест цифрения код, започващ с „SM“, който ще намерите в описанието на трансакцията. Ако не потвърдите банковата сметка, може да пропуснете плащането и абонаментът Ви да бъде спрян." + }, + "descriptorCode": { + "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": "Промяна на е-пощата" + }, + "removeMembers": { + "message": "Премахване на членовете" + }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Вие сте се вписали във всяко от устройствата по-долу. Ако не разпознавате някое от тях, премахнете го сега." + }, + "deviceListDescriptionTemp": { + "message": "Вашият акаунт е вписан във всяко от устройствата по-долу." + }, + "claimedDomains": { + "message": "Присвоени домейни" + }, + "claimDomain": { + "message": "Присвояване на домейн" + }, + "reclaimDomain": { + "message": "Повторно присвояване на домейн" + }, + "claimDomainNameInputHint": { + "message": "Пример: mydomain.com. Под-домейните изискват всеки отделен запис да бъде присвоен." + }, + "automaticClaimedDomains": { + "message": "Автоматично присвоени домейни" + }, + "automaticDomainClaimProcess": { + "message": "Битуорден ще се опита да присвои домейна 3 пъти през първите 72 часа. Ако той не може да бъде присвоен, проверете записа за DNS в сървъра си и направете присвояването ръчно. Домейнът ще бъде премахнат от организацията Ви след 7 дни, ако не бъде присвоен." + }, + "domainNotClaimed": { + "message": "Домейнът $DOMAIN$ не е присвоен. Проверете записите в DNS.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Присвоен" + }, + "domainStatusUnderVerification": { + "message": "В процес на проверка" + }, + "claimedDomainsDesc": { + "message": "Присвоете домейн, за да получите притежание върху всички членски акаунти, чиито е-пощи са с адрес от този домейн. Членовете ще могат да пропускат еднократното удостоверяване при вписване. Администраторите ще могат също така да изтриват членските акаунти." + }, + "invalidDomainNameClaimMessage": { + "message": "Неправилен формат. Пример: mydomain.com. Под-домейните изискват всеки отделен запис да бъде присвоен." + }, + "domainClaimedEvent": { + "message": "Домейнът $DOMAIN$ е присвоен", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "Домейнът $DOMAIN$ не е присвоен", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Ако премахнете $EMAIL$, спонсорирането за този семеен план няма да може да бъде получено. Наистина ли искате да продължите?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Ако премахнете $EMAIL$, спонсорирането за този семеен план ще бъде прекратено и от запазеното средство за разплащане ще бъдат взети 40$ + съответния данък на $DATE$. Няма да можете да получите друго спонсориране до $DATE$. Наистина ли искате да продължите?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Домейнът е присвоен" + }, + "organizationNameMaxLength": { + "message": "Името на организацията не може да бъде по-дълго от 50 знака." + }, + "resellerRenewalWarning": { + "message": "Вашият абонамент ще бъде подновен скоро. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Фактура за абонамента Ви беше издадена на $ISSUED_DATE$. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Фактурата за абонамента Ви не е била платена. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Абонаментът на организацията е рестартиран" + }, + "restartSubscription": { + "message": "Рестартирайте абонамента си" + }, + "suspendedManagedOrgMessage": { + "message": "Свържете се с $PROVIDER$ за помощ.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 934857a7d0f..b215e56e19a 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "সুরক্ষিত নোট" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "সংস্করণ $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 50d10c17db5..5f2605e71bc 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Sigurna bilješka" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Prijave" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Verzija $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 0974ed9b1a1..30649f3296d 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Inicis de sessió" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "L'inici de sessió amb el dispositiu ha d'estar activat a la configuració de l'aplicació Bitwarden. Necessiteu una altra opció?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Inici de sessió amb contrasenya mestra" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verificació de la vostra identitat" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "S'ha iniciat la sessió" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "S'ha enviat una notificació al vostre dispositiu." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versió $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Historial de les contrasenyes" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "No hi ha cap contrasenya a llistar." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Esborra", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Torneu a iniciar sessió." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Torneu a iniciar la sessió. Si esteu utilitzant altres aplicacions Bitwarden, tanqueu-les i torneu-les a obrir també." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Totes les sessions estan desautoritzades" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Aquest usuari fa servir l'inici de sessió en dues passes per protegir el seu compte." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Veure totes les opcions d'inici de sessió" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Dispositiu" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Actualitza el navegador" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Esteu utilitzant un navegador web no compatible. La caixa forta web pot no funcionar correctament." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Uneix-te a l'organització" }, @@ -4388,6 +4515,9 @@ "message": "No sol·liciteu tornar a comprovar la frase de les empremtes dactilars (No recomanat)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratuït", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Inicieu la sessió ràpidament mitjançant el portal d'inici de sessió únic de la vostra organització. Introduïu l'identificador de la vostra organització per començar." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Inici de sessió únic d'empresa" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Exclòs, no aplicable per a aquesta acció." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Empremta digital" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "L'administració d'usuaris també ha d'estar habilitada amb el permís de recuperació del compte de gestió" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Correu electrònic enviat" }, - "revokeSponsorshipConfirmation": { - "message": "Després de suprimir aquest compte, el propietari de l'organització Families serà responsable d'aquesta subscripció i de les factures relacionades. Esteu segur que voleu continuar?" - }, "removeSponsorshipSuccess": { "message": "S'ha suprimit el patrocini" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Requerit si l'identificador d'entitat no és un URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Personalització opcional" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipus de nom d'usuari" }, @@ -6681,6 +6857,10 @@ "message": "Proporcioneu automàticament usuaris i grups amb el vostre proveïdor d'identitat preferit mitjançant el subministrament SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Habilita SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "S'ha iniciat la sessió" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Cal l'aprovació del dispositiu. Seleccioneu una opció d'aprovació a continuació:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Recorda aquest dispositiu" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Aprova la sol·licitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hi ha sol·licituds de dispositiu" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Falta el correu electrònic de l'usuari" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositiu de confiança" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Administra el comportament de col·lecció per a l'organització" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limita la creació i la supressió de col·leccions als propietaris i administradors" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "afig mètode de pagament", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informació de l'organització" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Utilitzeu Bitwarden gestor de secrets SDK en els següents llenguatges de programació per crear les vostres aplicacions." }, - "setUpGithubActions": { - "message": "Configura les accions de Github" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Configura GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Configura Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "Veure el repositori C#" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "Veure el repositori C++" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "Veure el repositori JS WebAssembly" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "Veure el repositori de Java" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "Veure el repositori de Python" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "Veure el repositori php" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "Veure el repositori Ruby" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "Veure el repositori Go" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Creeu una nova organització client per gestionar com a proveïdor. Els seients addicionals es reflectiran en el proper cicle de facturació." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index c7a9b74c5db..7c8978fac80 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kritické aplikace" }, + "accessIntelligence": { + "message": "Přístup k inteligenci" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Poznatky o rizicích" }, "passwordRisk": { "message": "Rizikové heslo" }, - "discoverAtRiskPasswords": { - "message": "Objevte riziková hesla a upozorněte uživatele na změnu těchto hesel." + "reviewAtRiskPasswords": { + "message": "Zkontrolujte riziková hesla (slabá, odhalená nebo opakovaně používaná) ve všech aplikacích. Vyberte nejkritičtější aplikace a stanovte priority bezpečnostních opatření pro uživatele, abyste se vypořádali s rizikovými hesly." }, "dataLastUpdated": { "message": "Data naposledy aktualizována: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Obeznámení členové" }, + "revokeMembers": { + "message": "Odvolat členy" + }, + "restoreMembers": { + "message": "Obnovit členy" + }, + "cannotRestoreAccessError": { + "message": "Nelze obnovit přístup organizace" + }, "allApplicationsWithCount": { "message": "Všechny aplikace ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Zabezpečená poznámka" }, + "typeSshKey": { + "message": "SSH klíč" + }, "typeLoginPlural": { "message": "Přihlašovací údaje" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Přihlášení zařízením musí být nastaveno v aplikaci Bitwarden. Potřebujete další volby?" }, + "needAnotherOptionV1": { + "message": "Potřebujete další volby?" + }, "loginWithMasterPassword": { "message": "Přihlásit se pomocí hlavního hesla" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Přihlásit se do Bitwardenu" }, + "authenticationTimeout": { + "message": "Časový limit ověření" + }, + "authenticationSessionTimedOut": { + "message": "Vypršel časový limit relace ověřování. Restartujte proces přihlášení." + }, "verifyIdentity": { "message": "Ověřte svou totožnost" }, + "whatIsADevice": { + "message": "Co je to zařízení?" + }, + "aDeviceIs": { + "message": "Zařízení je jedinečná instalace aplikace Bitwarden, ve které jste se přihlásili. Přeinstalování, vymazání dat aplikace nebo vymazání souborů cookie může vést k tomu, že se zařízení objeví vícekrát." + }, "logInInitiated": { "message": "Bylo zahájeno přihlášení" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení." }, + "aNotificationWasSentToYourDevice": { + "message": "Na Vaše zařízení bylo odesláno oznámení" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Ujistěte se, že je Váš trezor odemčen a fráze otisku prstu se shodují s druhým zařízením" + }, "versionNumber": { "message": "Verze $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Historie hesel" }, + "generatorHistory": { + "message": "Historie generátoru" + }, + "clearGeneratorHistoryTitle": { + "message": "Vymazat historii generátoru" + }, + "cleargGeneratorHistoryDescription": { + "message": "Pokud budete pokračovat, všechny položky budou trvale smazány z historie generátoru. Jste si jisti, že chcete pokračovat?" + }, "noPasswordsInList": { "message": "Nejsou k dispozici žádná hesla." }, + "clearHistory": { + "message": "Vymazat historii" + }, + "nothingToShow": { + "message": "Nic k zobrazení" + }, + "nothingGeneratedRecently": { + "message": "Nedávno jste nic nevygenerovali" + }, "clear": { "message": "Vymazat", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Přihlaste se znovu." }, + "currentSession": { + "message": "Aktuální relace" + }, + "requestPending": { + "message": "Čekající požadavek" + }, "logBackInOthersToo": { "message": "Přihlaste se znovu. Používáte-li jiné aplikace Bitwardenu, přihlaste se znovu i v nich." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Všechny relace byly zrušeny" }, - "accountIsManagedMessage": { - "message": "Tento účet je spravován: $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Tento účet je vlastněn: $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Zbývá Vám 1 pozvánka." + }, "userUsingTwoStep": { "message": "Tento uživatel používá pro ochranu svého účtu dvoufázové přihlášení." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Zobrazit všechny volby přihlášení" + }, "viewAllLoginOptions": { "message": "Zobrazit všechny volby přihlášení" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Zařízení" }, + "loginStatus": { + "message": "Stav přihlášení" + }, + "firstLogin": { + "message": "První přihlášení" + }, + "trusted": { + "message": "Důvěryhodný" + }, "creatingAccountOn": { "message": "Vytváření účtu na" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Aktualizovat prohlížeč" }, + "generatingRiskInsights": { + "message": "Generování poznatků o rizicích..." + }, "updateBrowserDesc": { "message": "Používáte nepodporovaný webový prohlížeč. Webový trezor nemusí pracovat správně." }, + "freeTrialEndPromptCount": { + "message": "Vaše zkušební doba končí za $COUNT$ dnů.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, Vaše zkušební doba končí za $COUNT$ dnů.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, Vaše zkušební doba končí zítra.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Vaše zkušební verze končí zítra." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, Vaše zkušební doba končí dnes.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Vaše zkušební verze končí dnes." + }, + "clickHereToAddPaymentMethod": { + "message": "Klepněte zde pro přidání platební metody." + }, "joinOrganization": { "message": "Přidat se k organizaci" }, @@ -4388,6 +4515,9 @@ "message": "Nikdy se neptat na ověření frází otisků prstů pro pozvané uživatele (nedoporučeno)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Budete upozorněni, jakmile bude žádost schválena" + }, "free": { "message": "Zdarma", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Přihlaste se pomocí přihlašovacího portálu Vaší organizace. Chcete-li začít, zadejte identifikátor Vaší organizace." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Chcete-li začít, zadejte SSO identifikátor Vaší organizace" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Chcete-li se přihlásit u poskytovatele SSO, zadejte nejprve SSO identifikátor Vaší organizace. Možná budete muset zadat tento identifikátor SSO při přihlášení z nového zařízení." + }, "enterpriseSingleSignOn": { "message": "Jednotné podnikové přihlášení" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Vyloučené, neplatí pro tuto akci" }, + "nonCompliantMembersTitle": { + "message": "Nevyhovující členové" + }, + "nonCompliantMembersError": { + "message": "Členové, kteří nevyhovují zásadám jednotného přihlašování nebo dvoufázového přihlašování, nemohou být obnoveni, dokud nedodrží požadavky těchto zásad." + }, "fingerprint": { "message": "Otisk prstu" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "S oprávněním spravovat obnovení hesla musí být povolena také správa uživatelů" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-mail byl odeslán" }, - "revokeSponsorshipConfirmation": { - "message": "Po odebrání tohoto účtu vyprší sponzorování rodinného plánu na konci fakturační doby. Nebudete moci uplatnit novou nabídku sponzorování, dokud stávající neskončí. Opravdu chcete pokračovat?" - }, "removeSponsorshipSuccess": { "message": "Sponzorství bylo odebráno" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Vyžadováno, pokud ID entity není URL." }, + "offerNoLongerValid": { + "message": "Tato nabídka již není platná. Pro více informací kontaktujte správce organizace." + }, "openIdOptionalCustomizations": { "message": "Volitelné úpravy" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Vygenerovat e-mail" }, - "generatorBoundariesHint": { - "message": "Hodnota musí být mezi $MIN$ a $MAX$", + "spinboxBoundariesHint": { + "message": "Hodnota musí být mezi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Použijte $RECOMMENDED$ nebo více znaků k vytvoření silného hesla.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Použijte $RECOMMENDED$ slov nebo více slov k vytvoření silné heslové fráze.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Typ uživatelského jména" }, @@ -6681,6 +6857,10 @@ "message": "Automatické zajišťování uživatelů a skupin u preferovaného poskytovatele identit prostřednictvím zajišťování SCIM.", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatické zajišťování uživatelů a skupin u preferovaného poskytovatele identit prostřednictvím zajišťování SCIM. Najde podporované integrace.", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Povolit SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Bylo zahájeno přihlášení" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamatovat si toto zařízení pro bezproblémové budoucí přihlášení" + }, "deviceApprovalRequired": { "message": "Vyžaduje se schválení zařízení. Vyberte možnost schválení níže:" }, + "deviceApprovalRequiredV2": { + "message": "Vyžaduje se schválení zařízení" + }, + "selectAnApprovalOptionBelow": { + "message": "Vyberte volbu schválení níže" + }, "rememberThisDevice": { "message": "Zapamatovat toto zařízení" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Schválit žádost" }, + "deviceApproved": { + "message": "Zařízení schváleno" + }, + "deviceRemoved": { + "message": "Zařízení odebráno" + }, + "removeDevice": { + "message": "Odebrat zařízení" + }, + "removeDeviceConfirmation": { + "message": "Opravdu chcete odebrat toto zařízení?" + }, "noDeviceRequests": { "message": "Žádné žádosti ze zařízení" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Chybí e-mail uživatele" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktivní uživatelský e-mail nebyl nalezen. Budete odhlášeni." + }, "deviceTrusted": { "message": "Zařízení zařazeno mezi důvěryhodné" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Spravuje chování kolekce pro organizaci" }, - "limitCollectionCreationDeletionDesc": { - "message": "Omezí vytváření a mazání kolekce na vlastníky a správce" - }, "limitCollectionCreationDesc": { "message": "Omezí vytváření kolekce na vlastníky a správce" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "přidejte platební metodu", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informace o organizaci" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Použije SDK Správce tajných klíčů Bitwarden v následujících programovacích jazycích k vytvoření vlastních aplikací." }, - "setUpGithubActions": { - "message": "Nastavit akce GitHubu" + "ssoDescStart": { + "message": "Nastavit", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Nastavit Kubernetes" + "ssoDescEnd": { + "message": "Bitwarden pomocí průvodce implementací Vašeho poskytovatele identity.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Nastavit GitLab CI/CD" + "userProvisioning": { + "message": "Zajišťování uživatele" }, - "setUpAnsible": { - "message": "Nastavit Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "Zobrazit repozitář Rust" + "scimIntegrationDescStart": { + "message": "Nastavit ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "Zobrazit repozitář C#" + "scimIntegrationDescEnd": { + "message": "(Systém pro správu identity napříč doménami) automaticky poskytuje uživatelům a skupinám Bitwarden pomocí prováděcího průvodce pro Vašeho poskytovatele identity.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "Zobrazit repozitář C++" + "bwdc": { + "message": "Konektor adresáře Bitwarden" }, - "jsWebAssemblySDKRepo": { - "message": "Zobrazit repozitář JS WebAssembly" + "bwdcDesc": { + "message": "Nastaví konektor adresáře Bitwarden tak, aby automaticky poskytoval uživatele a skupiny pomocí implementačního průvodce pro Vašeho poskytovatele identity." }, - "javaSDKRepo": { - "message": "Zobrazit repozitář Java" + "eventManagement": { + "message": "Správa událostí" }, - "pythonSDKRepo": { - "message": "Zobrazit repozitář Python" + "eventManagementDesc": { + "message": "Integruje logy událostí Bitwardenu do systému SIEM (systémové informace a správu událostí) pomocí implementačního průvodce Vaší platformy." }, - "phpSDKRepo": { - "message": "Zobrazit repozitář PHP" + "deviceManagement": { + "message": "Správa zařízení" }, - "rubySDKRepo": { - "message": "Zobrazit repozitář Ruby" + "deviceManagementDesc": { + "message": "Nastaví správu zařízení pro Bitwarden pomocí implementačního průvodce pro Vaši platformu." }, - "goSDKRepo": { - "message": "Zobrazit repozitář Go" + "integrationCardTooltip": { + "message": "Spustí průvodce implementací $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Nastaví $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "Zobrazit repozitář $SDK$", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "otevře průvodce implementací $INTEGRATION$ na nové kartě.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "zobrazí repozitář $SDK$ na nové kartě.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "nastaví průvodce implementací $INTEGRATION$ na nové kartě.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Vytvořte novou klientskou organizaci pro správu jako poskytovatele. Další uživatelé budou reflektováni v dalším platebním cyklu." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Spravovaný poskytovatel služby" }, + "managedServiceProvider": { + "message": "Spravovaný poskytovatel služby" + }, + "multiOrganizationEnterprise": { + "message": "Podniky s více organizacemi" + }, "orgSeats": { "message": "Počet uživatelů v organizaci" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Aktualizované daňové údaje" }, + "billingInvalidTaxIdError": { + "message": "Neplatné DIČ. Pokud se domníváte, že se jedná o chybu, kontaktujte podporu." + }, + "billingTaxIdTypeInferenceError": { + "message": "Nebyli jsme schopni ověřit Vaše DIČ. Pokud se domníváte, že se jedná o chybu, kontaktujte podporu." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Neplatné DIČ. Pokud se domníváte, že se jedná o chybu, kontaktujte podporu." + }, + "billingPreviewInvoiceError": { + "message": "Při náhledu faktury došlo k chybě. Opakujte akci později." + }, "unverified": { "message": "Neověřeno" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB dodatečného úložiště" }, + "sshKeyAlgorithm": { + "message": "Algoritmus klíče" + }, + "sshKeyFingerprint": { + "message": "Otisk prstu" + }, + "sshKeyPrivateKey": { + "message": "Soukromý klíč" + }, + "sshKeyPublicKey": { + "message": "Veřejný klíč" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 bitový" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 bitový" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 bitový" + }, "premiumAccounts": { "message": "6 účtů Premium" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Vlastní hosting" }, - "verified-domain-single-org-warning": { - "message": "Ověřením domény se zapnou zásady jediné organizace." + "claim-domain-single-org-warning": { + "message": "Uplatněním domény se zapnou zásady jediné organizace." }, "single-org-revoked-user-warning": { "message": "Nevyhovujícím členům bude členství zrušeno. Správci mohou obnovit členy, jakmile opustí všechny ostatní organizace." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Pokud je člen smazán, jeho účet Bitwarden a individuální údaje z trezoru budou trvale smazány. Data kolekce zůstanou v organizaci. Pro jejich obnovení si musí vytvořit účet a být znovu zařazen do systému.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Tímto trvale smažete všechny položky vlastněné $NAME$. Položky kolekcí nejsou ovlivněny.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Tímto trvale smažete všechny položky vlastněné následujícími členy. Položky kolekcí nejsou ovlivněny.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "$NAME$ - smazán", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Uživatel byl odebrán z organizace a všechna přidružená uživatelská data byla smazána." + }, + "deletedUserId": { + "message": "Smazaný uživatel $ID$ - vlastník / správce smazal uživatelský účet", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Uživatel $ID$ opustil organizaci", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ je pozastaven", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Pro pomoc kontaktujte svého vlastníka organizace." + }, + "suspendedOwnerOrgMessage": { + "message": "Chcete-li získat přístup k Vaší organizaci, přidejte platební metodu." + }, + "deleteMembers": { + "message": "Smazat členy" + }, + "noSelectedMembersApplicable": { + "message": "Tuto akci nelze použít na žádného z vybraných členů." + }, + "deletedSuccessfully": { + "message": "Úspěšně smazáno" + }, + "freeFamiliesSponsorship": { + "message": "Odebrat sponzorství Free Bitwarden Families" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Nedovolí, aby členové mohli prostřednictvím této organizace uplatnit Free Bitwarden Families." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Platba bankovním účtem je dostupná jen pro zákazníky ve Spojených státech. Budete požádáni o ověření Vašeho bankovního účtu. Mikrovklad provedeme během následujících 1-2 pracovních dnů. Pro ověření bankovního účtu zadejte kód popisu výpisu z této zálohy na fakturační stránce organizace. Pokud bankovní účet neověříte, bude to mít za následek neprovedení platby a pozastavení předplatného." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Na Váš bankovní účet jsme provedli mikrovklad (může to trvat 1-2 pracovní dny). Zadejte šestimístný kód začínající písmenem \"SM\", který najdete v popisu vkladu. Pokud bankovní účet neověříte, bude to mít za následek neprovedení platby a pozastavení předplatného." + }, + "descriptorCode": { + "message": "Kód z popisu" + }, + "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" + }, + "removeMembers": { + "message": "Odebrat členy" + }, + "devices": { + "message": "Zařízení" + }, + "deviceListDescription": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení. Pokud zařízení nepoznáváte, odeberte jej nyní." + }, + "deviceListDescriptionTemp": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení." + }, + "claimedDomains": { + "message": "Uplatněné domény" + }, + "claimDomain": { + "message": "Uplatnit doménu" + }, + "reclaimDomain": { + "message": "Znovu uplatnit doménu" + }, + "claimDomainNameInputHint": { + "message": "Příklad: mojedoména.cz. Poddomény vyžadují samostatné záznamy, které je třeba ověřit." + }, + "automaticClaimedDomains": { + "message": "Automaticky uplatněné domény" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden se pokusí uplatnit doménu třikrát během prvních 72 hodin. Pokud doménu nelze uplatnit, zkontrolujte záznam DNS v hostitelském počítači a uplatněte ji ručně. Pokud se doménu nepodaří uplatnit, bude z Vaší organizace odebrána do 7 dnů." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ nebyla uplatněna. Zkontrolujte DNS záznamy.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Uplatněno" + }, + "domainStatusUnderVerification": { + "message": "V ověřování" + }, + "claimedDomainsDesc": { + "message": "Uplatněním domény získáte všechny členské účty, jejichž e-mailová adresa se shoduje s doménou. Členové budou moci při přihlašování přeskočit identifikátor SSO. Správci budou moci členské účty také mazat." + }, + "invalidDomainNameClaimMessage": { + "message": "Vstup není platný formát. Příklad: mojedoména.cz.Poddomény vyžadují samostatné záznamy, které je třeba uplatnit." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$: uplatněno", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$: neuplatněno", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Pokud odeberte $EMAIL$, nelze sponzorství pro tento rodinný plán uplatnit. Opravdu chcete pokračovat?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Pokud odeberete $EMAIL$, sponzorství tohoto rodinného plánu bude ukončeno. Z uloženého způsobu platby bude účtováno 40 USD + příslušná daň a to dne $DATE$. Nové sponzorství budete moci uplatnit až od $DATE$. Jste si jisti, že chcete pokračovat?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Doména uplatněna" + }, + "organizationNameMaxLength": { + "message": "Název organizace nesmí přesáhnout 50 znaků." + }, + "resellerRenewalWarning": { + "message": "Vaše předplatné se brzy obnoví. Chcete-li zajistit nepřerušenou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Faktura pro Vaše předplatné byla vystavena dne $ISSUED_DATE$. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Faktura za Vaše předplatné nebyla zaplacena. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Předplatné organizace bylo restartováno" + }, + "restartSubscription": { + "message": "Restartovat Vaše předplatné" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktujte $PROVIDER$ pro pomoc.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 4ca57a56bf2..ed8a03eb8b0 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 25e85fef58b..9731c344e6e 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -3,16 +3,19 @@ "message": "Alle applikationer" }, "criticalApplications": { - "message": "Kritiske applikationer" + "message": "Kritiske apps" + }, + "accessIntelligence": { + "message": "Adgangsefterretning" }, "riskInsights": { - "message": "Risk Insights" + "message": "Risikoindsigt" }, "passwordRisk": { "message": "Adgangskoderisiko" }, - "discoverAtRiskPasswords": { - "message": "Opdag udsatte adgangskoder og underret brugerne om at ændre disse." + "reviewAtRiskPasswords": { + "message": "Gennemgå risikobetonede adgangskoder (svage, eksponerede eller genbrugte) på tværs af applikationer. Vælg de mest kritiske applikationer for at prioritere sikkerhedshandlinger for brugerne til at håndtere risikobetonede adgangskoder." }, "dataLastUpdated": { "message": "Data senest opdateret: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Underrettede medlemmer" }, + "revokeMembers": { + "message": "Ophæv medlemmer" + }, + "restoreMembers": { + "message": "Gendan medlemmer" + }, + "cannotRestoreAccessError": { + "message": "Kan ikke gendanne organisationsadgang" + }, "allApplicationsWithCount": { "message": "Alle applikationer ($COUNT$)", "placeholders": { @@ -39,7 +51,7 @@ "message": "Opret nyt login-emne" }, "criticalApplicationsWithCount": { - "message": "Kritiske applikationer ($COUNT$)", + "message": "Kritiske apps ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -57,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "Ingen applikationer fundet i $ORG NAVN$", + "message": "Ingen apps fundet i $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -66,16 +78,16 @@ } }, "noAppsInOrgDescription": { - "message": "Når brugerne gemmer logins, fremgår applikationer med udsatte adgangskoder her. Markér kritiske appikationer og giv brugerne besked om at opdatere adgangskoder." + "message": "Når brugerne gemmer logins, vil apps med udsatte adgangskoder fremgå her. Markér kritiske apps og giv brugerne besked om at opdatere adgangskoder." }, "noCriticalAppsTitle": { - "message": "Ingen applikationer markret som Kritisk" + "message": "Ingen apps markret som Kritisk" }, "noCriticalAppsDescription": { - "message": "Vælg de mest kritiske applikationer for at afsløre udsatte adgangskoder og give brugerne besked om at ændre disse." + "message": "Vælg de mest kritiske apps for at afsløre udsatte adgangskoder og give brugerne besked om at ændre disse." }, "markCriticalApps": { - "message": "Markér kritiske applikationer" + "message": "Markér kritiske apps" }, "markAppAsCritical": { "message": "Markér app som kritisk" @@ -96,7 +108,7 @@ "message": "Adgangskoder i alt" }, "searchApps": { - "message": "Søg i applikationer" + "message": "Søg i apps" }, "atRiskMembers": { "message": "Udsatte medlemmer" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Sikret notat" }, + "typeSshKey": { + "message": "SSH-nøgle" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log ind med enhed skal være opsat i indstillingerne i Bitwarden-appen. Brug for en anden mulighed?" }, + "needAnotherOptionV1": { + "message": "Behov for en anden mulighed?" + }, "loginWithMasterPassword": { "message": "Log ind med hovedadgangskode" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log ind på Bitwarden" }, + "authenticationTimeout": { + "message": "Godkendelsestimeout" + }, + "authenticationSessionTimedOut": { + "message": "Godkendelsessessionen fik timeout. Genstart loginprocessen." + }, "verifyIdentity": { "message": "Bekræft din identitet" }, + "whatIsADevice": { + "message": "Hvad er en enhed?" + }, + "aDeviceIs": { + "message": "En enhed er en unik installation af Bitwarden-appen, hvor man har logget ind. Geninstallation, rydning af app-data eller rydning af cookies kan medføre, at en enhed vises op flere gange." + }, "logInInitiated": { "message": "Indlogning påbegyndt" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." }, + "aNotificationWasSentToYourDevice": { + "message": "En notifikation er sendt til enheden" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Sørg for, at boksen er oplåst, samt at fingeraftrykssætningen matcher på den anden enhed" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Adgangskodehistorik" }, + "generatorHistory": { + "message": "Generatorhistorik" + }, + "clearGeneratorHistoryTitle": { + "message": "Ryd generatorhistorik" + }, + "cleargGeneratorHistoryDescription": { + "message": "Fortsætter man, slettes alle poster permanent fra generatorhistorikken. Sikker på, at handlingen skal udføres?" + }, "noPasswordsInList": { "message": "Der er ingen adgangskoder at vise." }, + "clearHistory": { + "message": "Ryd historik" + }, + "nothingToShow": { + "message": "Intet at vise" + }, + "nothingGeneratedRecently": { + "message": "Intet genereret for nylig" + }, "clear": { "message": "Ryd", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Log ind igen." }, + "currentSession": { + "message": "Aktuel session" + }, + "requestPending": { + "message": "Anmodning afventer" + }, "logBackInOthersToo": { "message": "Log ind igen. Bruger du andre Bitwarden-applikationer, så log også ud og ind igen på disse." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Godkendelser for alle sessioner fjernet" }, - "accountIsManagedMessage": { - "message": "Denne konto håndteres af $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Denne konto ejes af $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Der er 1 invitation tilbage." + }, "userUsingTwoStep": { "message": "Denne bruger benytter totrins-login for at beskytte kontoen." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Vis alle indlogningsmuligheder" + }, "viewAllLoginOptions": { "message": "Vis alle indlogningsmuligheder" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Enhed" }, + "loginStatus": { + "message": "Indlogningsstatus" + }, + "firstLogin": { + "message": "Første login" + }, + "trusted": { + "message": "Betroet" + }, "creatingAccountOn": { "message": "Opretter konto på" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Opdatér browser" }, + "generatingRiskInsights": { + "message": "Genererer risikoindsigter..." + }, "updateBrowserDesc": { "message": "Du bruger en ikke-understøttet webbrowser. Web-boksen fungerer muligvis ikke korrekt." }, + "freeTrialEndPromptCount": { + "message": "Den gratis prøveperiode slutter om $COUNT$ dage.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, den gratis prøveperiode slutter om $COUNT$ dage.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, den gratis prøveperiode slutter i morgen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Den gratis prøveperiode slutter i morgen." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, den gratis prøveperiode slutter i dag.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Den gratis prøveperiode slutter i dag." + }, + "clickHereToAddPaymentMethod": { + "message": "Klik her for at tilføje en betalingsmetode." + }, "joinOrganization": { "message": "Bliv medlem af organisation" }, @@ -4388,6 +4515,9 @@ "message": "Bed aldrig om at bekræfte fingeraftrykssætninger for inviterede brugere (anbefales ikke)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Man vil få besked, når anmodningen er godkendt" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log ind via din organisations single sign-on portal. Angiv organisationens SSO-identifikator for at begynde." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Angiv organisationens SSO-identifikator for at begynde" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "For at logge ind med sin SSO-udbyder skal man angive sin organisations SSO-identifikator for at begynde. Man skal muligvis angive denne SSO-identifikator ved indlogning fra en ny enhed." + }, "enterpriseSingleSignOn": { "message": "Virksomheds Single Sign-On" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Udelukket, ikke anvendelig til denne handling" }, + "nonCompliantMembersTitle": { + "message": "Uoverensstemmende medlemmer" + }, + "nonCompliantMembersError": { + "message": "Medlemmer, som ikke overholder enkelt organisations- eller totrins login-politikken, kan ikke genoprettes, før de er i overholder politikkravene" + }, "fingerprint": { "message": "Fingeraftryk" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Håndtér brugere skal også tildeles med tilladelsen håndtér kontogendannelse" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-mail sendt" }, - "revokeSponsorshipConfirmation": { - "message": "Efter fjernelse af denne konto udløber Familie-abonnementets sponsorat ved slutningen af faktureringsperioden. Du vil ikke kunne indløse et nyt sponsortilbud, før det eksisterende udløber. Sikker på, at du vil fortsætte?" - }, "removeSponsorshipSuccess": { "message": "Sponsoratet fjernet" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Obligatorisk, såfremt Enheds-ID ikke er en URL." }, + "offerNoLongerValid": { + "message": "Dette tilbud er udløbet. Kontakt organisationsadministratorer for mere information." + }, "openIdOptionalCustomizations": { "message": "Valgfrie tilpasninger" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generér e-mail" }, - "generatorBoundariesHint": { - "message": "Værdi skal være mellem $MIN$ og $MAX$", + "spinboxBoundariesHint": { + "message": "Værdi skal være mellem $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Brug minimum $RECOMMENDED$ tegn for at generere en stærk adgangskode.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Brug minimum $RECOMMENDED$ ord for at generere en stærk adgangssætning.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Brugernavnstype" }, @@ -6681,6 +6857,10 @@ "message": "Tildel automatisk brugere og grupper den foretrukne identitetsudbyder via SCIM-provisionering", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Tildel automatisk brugere og grupper den foretrukne identitetsudbyder via SCIM-provisionering. Find understøttede integrationer", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Aktivér SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Indlogning påbegyndt" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Husk denne enhed for at gøre fremtidige indlogninger gnidningsløse" + }, "deviceApprovalRequired": { "message": "Enhedsgodkendelse kræves. Vælg en godkendelsesmulighed nedenfor:" }, + "deviceApprovalRequiredV2": { + "message": "Enhedsgodkendelse kræves" + }, + "selectAnApprovalOptionBelow": { + "message": "Vælg en godkendelsesmulighed nedenfor" + }, "rememberThisDevice": { "message": "Husk denne enhed" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Godkend anmodning" }, + "deviceApproved": { + "message": "Enhed godkendt" + }, + "deviceRemoved": { + "message": "Enhed fjernet" + }, + "removeDevice": { + "message": "Fjern enhed" + }, + "removeDeviceConfirmation": { + "message": "Sikker på, at denne enhed skal fjernes?" + }, "noDeviceRequests": { "message": "Ingen enhedsanmodninger" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Brugers e-mail mangler" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiv bruger e-mail ikke fundet. Man logges ud." + }, "deviceTrusted": { "message": "Enhed betroet" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Håndtér samlingsadfærden for organisationen" }, - "limitCollectionCreationDeletionDesc": { - "message": "Begræns samlingsoprettelse og -sletning til ejere og admins" - }, "limitCollectionCreationDesc": { "message": "Begræns samlingsoprettelse til ejere og admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "tilføj en betalingsmetode", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisationsoplysninger" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Brug Bitwarden Secrets Manager SDK i flg. programmeringssprog til bygning af egne applikationer." }, - "setUpGithubActions": { - "message": "Opsæt Github-handlinger" + "ssoDescStart": { + "message": "Opsæt", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden vha. implementeringsvejledningen for den aktuelle Identitetsudbyder.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Brugerprovisionering" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Opsæt Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System til håndtering af Kryds-domæneidentitet) for automatisk at tildele brugere og grupper til Bitwarden vha. implementeringsvejledningen for den aktuelle Identitetsudbyder.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Opsæt GitLab CI/CD" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Opsæt Ansible" + "bwdcDesc": { + "message": "Opsæt Bitwarden Directory Connector til automatisk at tildele brugere og grupper vha. implementeringsvejledningen for den aktuelle Identitetsudbyder." }, - "rustSDKRepo": { - "message": "Vis Ruby-repo" + "eventManagement": { + "message": "Begivenhedshåndtering" }, - "cSharpSDKRepo": { - "message": "Vis C#-repo" + "eventManagementDesc": { + "message": "Integrér Bitwarden-begivenhedslogger med det aktuelle SIEM (systeminformation og begivenhedshåndtering) system vha. implementeringsvejledningen til den aktuelle platform." }, - "cPlusPlusSDKRepo": { - "message": "Vis C++-repo" + "deviceManagement": { + "message": "Enhedshåndtering" }, - "jsWebAssemblySDKRepo": { - "message": "VIs JS WebAssembly-repo" + "deviceManagementDesc": { + "message": "Opsæt enhedshåndtering for Bitwarden vha. implementeringsvejledningen for den aktuelle platform." }, - "javaSDKRepo": { - "message": "Vis Java-repo" + "integrationCardTooltip": { + "message": "Start $INTEGRATION$-implementeringsguiden.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Opsæt $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Vis Python-repo" + "smSdkTooltip": { + "message": "Vis $SDK$-repo", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Vis php-repo" + "integrationCardAriaLabel": { + "message": "åbn $INTEGRATION$-implementeringsguiden på en ny fane.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Vis Ruby-repo" + "smSdkAriaLabel": { + "message": "se $SDK$-repo på en ny fane.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Vis Go-repo" + "smIntegrationCardAriaLabel": { + "message": "opset $INTEGRATION$-implementeringsguiden på en ny fane.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Opret en ny kundeorganisation til at håndtere som udbyder. Yderligere pladser afspejles i næste faktureringscyklus." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Administreret Tjenesteudbyder" }, + "managedServiceProvider": { + "message": "Håndteret tjenesteudbyder" + }, + "multiOrganizationEnterprise": { + "message": "Multiorganisationsvirksomhed" + }, "orgSeats": { "message": "Organisationspladser" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Opdaterede momsoplysninger" }, + "billingInvalidTaxIdError": { + "message": "Ugyldigt moms-ID. Mener man, at dette er en fejl, kontakt venligst supporten." + }, + "billingTaxIdTypeInferenceError": { + "message": "Moms-ID kan ikke bekræftes. Mener man, at dette er en fejl, kontakt venligst supporten." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Ugyldigt moms-ID. Mener man, at dette er en fejl, kontakt venligst supporten." + }, + "billingPreviewInvoiceError": { + "message": "En fejl opstod under forhåndsvisning af fakturaen. Forsøg igen senere." + }, "unverified": { "message": "Ubekræftet" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB ekstra lagerplads" }, + "sshKeyAlgorithm": { + "message": "Nøglealgoritme" + }, + "sshKeyFingerprint": { + "message": "Fingeraftryk" + }, + "sshKeyPrivateKey": { + "message": "Privat nøgle" + }, + "sshKeyPublicKey": { + "message": "Offentlig nøgle" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 Premium-konti" }, @@ -9544,16 +9842,16 @@ "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "For hoste Bitwarden på egen server, skal man uploade sin licensfil. Opsæt automatisk faktureringssynk for den selv-hostede organisation for at understøtte Free Families-abonnementstyper og avancerede faktureringsmuligheder for organisationen." + "message": "For at hoste Bitwarden på egen server skal man uploade sin licensfil. Opsæt automatisk faktureringssynk for den selv-hostede organisation for at understøtte Free Families-abonnementstyper og avancerede faktureringsmuligheder for organisationen." }, "selfHostingTitleProper": { "message": "Selv-hosting" }, - "verified-domain-single-org-warning": { - "message": "Bekræftelse af et domæne vil slå den enkelte organisationspolitik til." + "claim-domain-single-org-warning": { + "message": "Registrering af et domæne vil slå den enkelte organisationspolitik til." }, "single-org-revoked-user-warning": { - "message": "Ikke-overholdende medlemmers privilegier ophæves. Administratorer kan gendanne medlemmer, når overholdelse er opfyldt." + "message": "Ikke-overholdende medlemmers privilegier ophæves. Administratorer kan gendanne medlemmer, når de har forladt alle øvrige organisationer." }, "deleteOrganizationUser": { "message": "Slet $NAME$", @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Når medlemmer slettes, vil deres Bitwarden-konto og individuelle boksdata blive slettet permanent. Indsamlingsdata vil forblive i organisationen. For at genindsætte dem, skal de oprette en konto og onboarderes igen.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Dette sletter permanent alle elementer ejet af $NAME$. Samlingsemner berøres ikke.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Dette sletter permanent alle elementer ejet af flg. medlemmer. Samlingsemner berøres ikke.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Slet $NAME$", @@ -9579,6 +9887,254 @@ } }, "organizationUserDeletedDesc": { - "message": "Brugeren er fjernet fra organisationen og alle tilknyttede brugerdata er slettet." + "message": "Brugeren er fjernet fra organisationen, og alle tilknyttede brugerdata er slettet." + }, + "deletedUserId": { + "message": "Slettet bruger $ID$ - en ejer/admin har slettet brugerkontoen", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Brugeren $ID$ har forladt organisationen", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ er suspenderet", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Kontakt organisationsejeren for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "Tilføj en betalingsmetode for at få adgang til organisationen igen." + }, + "deleteMembers": { + "message": "Slet medlemmer" + }, + "noSelectedMembersApplicable": { + "message": "Denne handling er ikke anvendelig til nogen af de valgte medlemmer." + }, + "deletedSuccessfully": { + "message": "Slettet" + }, + "freeFamiliesSponsorship": { + "message": "Fjern Gratis Bitwarden Families-sponsorering" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Tillad ikke medlemmer at indløse en Families-abonnementstype via denne organisation." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Betaling via bankkonto er kun tilgængelig for kunder i USA. Man anmodes om at bekræfte sin bankkonto. Vi vil foretage en mikro-indbetaling inden for de næste 1-2 arbejdsdage. Indtast kontoudtogsbeskrivelseskoden fra denne indbetaling på organisationens faktureringsside for at bekræfte bankkontoen. Manglende bekræftelse af bankkontoen vil resultere i manglende betaling, hvorefter abonnementet suspenderes." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Vi har foretaget en mikro-indbetaling på den oplyste bankkonto (hvilket kan tage 1-2 hverdage). Angiv den sekscifrede kode startende med 'SM' fra på indbetalingsbeskrivelsen. Manglende bekræftelse af bankkontoen vil resultere i manglende betaling, hvorefter abonnementet suspenderes." + }, + "descriptorCode": { + "message": "Beskrivelseskode" + }, + "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, muligvis ikke" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Ja, e-mailadressen kan pålideligt tilgås" + }, + "turnOnTwoStepLogin": { + "message": "Slå totrins-login til" + }, + "changeAcctEmail": { + "message": "Skift kontoe-mailadresse" + }, + "removeMembers": { + "message": "Fjern medlemmer" + }, + "devices": { + "message": "Enheder" + }, + "deviceListDescription": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor. Genkendes en enhed ikke, fjern den nu." + }, + "deviceListDescriptionTemp": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor." + }, + "claimedDomains": { + "message": "Registrerede domæner" + }, + "claimDomain": { + "message": "Registrering af domæne" + }, + "reclaimDomain": { + "message": "Genregistrering af domæne" + }, + "claimDomainNameInputHint": { + "message": "Eks.: mitdomaene.dk. Underdomæner kræver, at separate poster registreres." + }, + "automaticClaimedDomains": { + "message": "Automatisk Registrerede Domæner" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden vil forsøge at registrere domænet 3 gange i løbet af de første 72 timer. Kan domænet ikke registreres, tjek DNS-posten på værten og registrér manuelt. Såfremt uregistreret efter 7 dage, fjernes domænet fra organisationen." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ ikke registreret. Tjek DNS-posterne.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Registreret" + }, + "domainStatusUnderVerification": { + "message": "Under verifikation" + }, + "claimedDomainsDesc": { + "message": "Registrér et domæne for at eje alle medlemskonti, hvis e-mailadresse matcher domænet. Medlemmer vil kunne overspringe SSO-identifikatoren under indlogning. Administratorer vil også kunne slette medlemskonti." + }, + "invalidDomainNameClaimMessage": { + "message": "Input er ikke i et gyldigt format. Format: mitdomaene.dk. Underdomæner kræver, at separate poster registreres." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ registreret", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ ikke registreret", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Fjernes $EMAIL$, kan sponsoratet for denne Family-abonnementstype ikke indløses. Fortsæt alligevel?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Fjerner $EMAIL$, ophører sponsoratet for denne Family-abonnementstype, og der opkræves $40 + gældende skat pr. $DATE$ via den registrerede betalingsmetode. Et nyt sponsorat vil ikke kunne indløses før $DATE$. Fortsæt alligevel?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domæne registreret" + }, + "organizationNameMaxLength": { + "message": "Organisationsnavn må ikke overstige 50 tegn." + }, + "resellerRenewalWarning": { + "message": "Abonnementet fornyes snart. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "En faktura for abonnementet er udstedt pr. $ISSUED_DATE$. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Fakturaen for abonnementet er ikke blevet betalt. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organisationsabonnement genstartet" + }, + "restartSubscription": { + "message": "Genstart abonnementet" + }, + "suspendedManagedOrgMessage": { + "message": "Kontakt $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 01a34c0afb7..540db2dec05 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kritische Anwendungen" }, + "accessIntelligence": { + "message": "Zugriff auf Informationen" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Risiko-Überblick" }, "passwordRisk": { "message": "Passwort-Risiko" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Überprüfe gefährdete Passwörter (schwach, kompromittiert oder wiederverwendet) in allen Anwendungen. Wähle deine kritischsten Anwendungen aus, um die Sicherheitsmaßnahmen für deine Benutzer zu priorisieren, um gefährdete Passwörter zu beseitigen." }, "dataLastUpdated": { "message": "Daten zuletzt aktualisiert: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Benachrichtigte Mitglieder" }, + "revokeMembers": { + "message": "Mitglieder widerrufen" + }, + "restoreMembers": { + "message": "Mitglieder wiederherstellen" + }, + "cannotRestoreAccessError": { + "message": "Organisationszugriff kann nicht wiederhergestellt werden" + }, "allApplicationsWithCount": { "message": "Alle Anwendungen ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Sichere Notiz" }, + "typeSshKey": { + "message": "SSH-Schlüssel" + }, "typeLoginPlural": { "message": "Zugangsdaten" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Die Anmeldung über ein Gerät muss in den Einstellungen der Bitwarden App eingerichtet werden. Benötigst du eine andere Option?" }, + "needAnotherOptionV1": { + "message": "Brauchst du eine andere Option?" + }, "loginWithMasterPassword": { "message": "Mit Master-Passwort anmelden" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Bei Bitwarden anmelden" }, + "authenticationTimeout": { + "message": "Authentifizierungs-Timeout" + }, + "authenticationSessionTimedOut": { + "message": "Die Authentifizierungssitzung ist abgelaufen. Bitte starte den Anmeldeprozess neu." + }, "verifyIdentity": { "message": "Verifiziere deine Identität" }, + "whatIsADevice": { + "message": "Was ist ein \"Gerät\"?" + }, + "aDeviceIs": { + "message": "Ein \"Gerät\" ist eine einzigartige Installation der Bitwarden-App, in der du dich angemeldet hast. Eine Neuinstallation, das Löschen von App-Daten oder das Löschen von Cookies könnte dazu führen, dass ein \"Gerät\" mehrfach erscheint." + }, "logInInitiated": { "message": "Anmeldung eingeleitet" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet." }, + "aNotificationWasSentToYourDevice": { + "message": "Eine Benachrichtigung wurde an dein Gerät gesendet" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Stelle sicher, dass dein Konto entsperrt ist und die Fingerabdruck-Phrase mit der vom anderen Gerät übereinstimmt" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Passwortverlauf" }, + "generatorHistory": { + "message": "Generator-Verlauf" + }, + "clearGeneratorHistoryTitle": { + "message": "Generator-Verlauf löschen" + }, + "cleargGeneratorHistoryDescription": { + "message": "Wenn du fortfährst, werden alle Einträge dauerhaft aus dem Generator-Verlauf gelöscht. Bist du sicher, dass du fortfahren möchtest?" + }, "noPasswordsInList": { "message": "Keine Passwörter vorhanden." }, + "clearHistory": { + "message": "Verlauf löschen" + }, + "nothingToShow": { + "message": "Nichts anzuzeigen" + }, + "nothingGeneratedRecently": { + "message": "Du hast in letzter Zeit nichts generiert" + }, "clear": { "message": "Löschen", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Bitte melden Sie sich erneut an." }, + "currentSession": { + "message": "Aktuelle Sitzung" + }, + "requestPending": { + "message": "Anfrage ausstehend" + }, "logBackInOthersToo": { "message": "Bitte melden Sie sich wieder an. Wenn Sie andere Bitwarden-Anwendungen verwenden, melden Sie sich auch dort ab und wieder neu an." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Alle Sitzungen wurden abgemeldet" }, - "accountIsManagedMessage": { - "message": "Dieses Konto wird von $ORGANIZATIONNAME$ verwaltet", + "accountIsOwnedMessage": { + "message": "Dieses Konto ist im Besitz von $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Du hast noch eine Einladung übrig." + }, "userUsingTwoStep": { "message": "Dieser Benutzer hat sein Konto mit einer Zwei-Faktor-Authentifizierung geschützt." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Alle Anmeldeoptionen anzeigen" + }, "viewAllLoginOptions": { "message": "Alle Anmeldeoptionen anzeigen" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Gerät" }, + "loginStatus": { + "message": "Anmeldestatus" + }, + "firstLogin": { + "message": "Erste Anmeldung" + }, + "trusted": { + "message": "Vertrauenswürdig" + }, "creatingAccountOn": { "message": "Konto wird erstellt bei" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Browser aktualisieren" }, + "generatingRiskInsights": { + "message": "Dein Risiko-Überblick wird generiert..." + }, "updateBrowserDesc": { "message": "Du verwendest einen nicht unterstützten Webbrowser. Der Web-Tresor funktioniert möglicherweise nicht richtig." }, + "freeTrialEndPromptCount": { + "message": "Deine kostenlose Testversion endet in $COUNT$ Tagen.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, deine kostenlose Testversion endet in $COUNT$ Tagen.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, deine kostenlose Testversion endet morgen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Deine kostenlose Testversion endet morgen." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, deine kostenlose Testversion endet heute.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Deine kostenlose Testversion endet heute." + }, + "clickHereToAddPaymentMethod": { + "message": "Klick hier, um eine Zahlungsmethode hinzuzufügen." + }, "joinOrganization": { "message": "Organisation beitreten" }, @@ -4388,6 +4515,9 @@ "message": "Keine Aufforderung zur Überprüfung von Fingerabdruck-Phrasen für eingeladene Benutzer (nicht empfohlen)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Du wirst benachrichtigt, sobald die Anfrage genehmigt wurde" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Melde dich über das Single Sign-on-Portal deiner Organisation an. Bitte gib die SSO-Kennung deiner Organisation ein, um zu beginnen." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Gib die SSO-Kennung deiner Organisation ein, um zu starten" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Um dich bei deinem SSO-Anbieter anzumelden, gib zunächst die SSO-Kennung deiner Organisation ein. Du musst diese SSO-Kennung möglicherweise eingeben, wenn du dich von einem neuen Gerät aus anmeldest." + }, "enterpriseSingleSignOn": { "message": "Enterprise Single Sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Ausgeschlossen, nicht anwendbar für diese Aktion." }, + "nonCompliantMembersTitle": { + "message": "Nicht konforme Mitglieder" + }, + "nonCompliantMembersError": { + "message": "Mitglieder, die die Richtlinien für die Anmeldung bei einer Einzelorganisation oder für die Zwei-Faktor-Authentifizierung nicht einhalten, können nicht wiederhergestellt werden, bis sie die Anforderungen der Richtlinien erfüllen" + }, "fingerprint": { "message": "Fingerabdruck" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiere das Kundenerfolgsteam", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Benutzer verwalten\" muss zusammen mit der \"Kontowiederherstellung verwalten\"-Berechtigung erteilt werden" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-Mail gesendet" }, - "revokeSponsorshipConfirmation": { - "message": "Nach dem Entfernen dieses Kontos läuft das Sponsoring des Families-Abonnements am Ende des Abrechnungszeitraums ab. Du wirst kein neues Sponsoringangebot einlösen können, bis das bestehende abläuft. Bist du sicher, dass du fortfahren möchtest?" - }, "removeSponsorshipSuccess": { "message": "Sponsoring entfernt" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Erforderlich, wenn die Entitäts-ID keine URL ist." }, + "offerNoLongerValid": { + "message": "Dieses Angebot ist nicht mehr gültig. Kontaktiere die Administratoren deiner Organisation für weitere Informationen." + }, "openIdOptionalCustomizations": { "message": "Optionale Anpassungen" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "E-Mail generieren" }, - "generatorBoundariesHint": { - "message": "Wert muss zwischen $MIN$ und $MAX$ liegen", + "spinboxBoundariesHint": { + "message": "Wert muss zwischen $MIN$ und $MAX$ liegen.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Verwende $RECOMMENDED$ oder mehr Zeichen, um ein starkes Passwort zu generieren.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Verwende $RECOMMENDED$ oder mehr Wörter, um eine starke Passphrase zu generieren.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Benutzernamenstyp" }, @@ -6681,6 +6857,10 @@ "message": "Über SCIM-Bereitstellung automatisch Benutzer und Gruppen mit deinem bevorzugten Identitätsanbieter zur Verfügung stellen", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatische Bereitstellung von Benutzern und Gruppen mit deinem bevorzugten Identitätsanbieter über SCIM-Bereitstellung. Suche nach unterstützten Integrationen", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM aktivieren", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Anmeldung eingeleitet" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Dieses Gerät merken, um zukünftige Anmeldungen reibungslos zu gestalten" + }, "deviceApprovalRequired": { "message": "Geräte-Genehmigung erforderlich. Wähle unten eine Genehmigungsoption aus:" }, + "deviceApprovalRequiredV2": { + "message": "Geräte-Genehmigung erforderlich" + }, + "selectAnApprovalOptionBelow": { + "message": "Wähle unten eine Genehmigungsoption aus" + }, "rememberThisDevice": { "message": "Dieses Gerät merken" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Anfrage genehmigen" }, + "deviceApproved": { + "message": "\"Gerät\" genehmigt" + }, + "deviceRemoved": { + "message": "\"Gerät\" entfernt" + }, + "removeDevice": { + "message": "\"Gerät\" entfernen" + }, + "removeDeviceConfirmation": { + "message": "Möchtest du das \"Gerät\" wirklich entfernen?" + }, "noDeviceRequests": { "message": "Keine Geräteanfragen" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "E-Mail-Adresse des Benutzers fehlt" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktive Benutzer-E-Mail-Adresse nicht gefunden. Du wirst abgemeldet." + }, "deviceTrusted": { "message": "Gerät wird vertraut" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Verwalte das Sammlungsverhalten für die Organisation" }, - "limitCollectionCreationDeletionDesc": { - "message": "Sammlungserstellung und -löschung auf Eigentümer und Administratoren beschränken" - }, "limitCollectionCreationDesc": { "message": "Das Erstellen von Sammlungen auf Eigentümer und Administratoren beschränken" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "füge eine Zahlungsmethode hinzu", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisationsinformationen" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Verwende das Bitwarden Secrets Manager SDK in den folgenden Programmiersprachen, um deine eigenen Anwendungen zu erstellen." }, - "setUpGithubActions": { - "message": "GitHub Actions einrichten" + "ssoDescStart": { + "message": "Konfiguriere", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "für Bitwarden mithilfe der Implementierungsanleitung für deinen Identitätsanbieter.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Benutzer-Bereitstellung" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Konfiguriere ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Kubernetes einrichten" + "scimIntegrationDescEnd": { + "message": "(System für Cross-Domain Identity Management) zur automatischen Bereitstellung von Benutzern und Gruppen an Bitwarden mithilfe der Implementierungsanleitung für deinen Identitätsanbieter.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "GitLab CI/CD einrichten" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Ansible einrichten" + "bwdcDesc": { + "message": "Konfiguriere den Bitwarden Directory Connector so, dass Benutzer und Gruppen automatisch mithilfe der Implementierungsanleitung für deinen Identitätsanbieter bereitgestellt werden." }, - "rustSDKRepo": { - "message": "Rust-Repository anzeigen" + "eventManagement": { + "message": "Ereignisverwaltung" }, - "cSharpSDKRepo": { - "message": "C#-Repository anzeigen" + "eventManagementDesc": { + "message": "Integriere Bitwarden Ereignis-Protokolle in dein SIEM (Systeminformation und Eventmanagement), indem du die Implementierungsanleitung für deine Plattform verwendest." }, - "cPlusPlusSDKRepo": { - "message": "C++-Repository anzeigen" + "deviceManagement": { + "message": "Geräteverwaltung" }, - "jsWebAssemblySDKRepo": { - "message": "JS WebAssembly-Repository anzeigen" + "deviceManagementDesc": { + "message": "Konfiguriere die Geräteverwaltung für Bitwarden mithilfe der Implementierungsanleitung für deine Plattform." }, - "javaSDKRepo": { - "message": "Java-Repository anzeigen" + "integrationCardTooltip": { + "message": "$INTEGRATION$-Implementierungsanleitung starten.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "$INTEGRATION$ einrichten.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Python-Repository anzeigen" + "smSdkTooltip": { + "message": "$SDK$-Repository anzeigen", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "PHP-Repository anzeigen" + "integrationCardAriaLabel": { + "message": "$INTEGRATION$-Implementierungsanleitung in einem neuen Tab öffnen.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Ruby-Repository anzeigen" + "smSdkAriaLabel": { + "message": "$SDK$-Repository in einem neuen Tab anzeigen.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Go-Repository anzeigen" + "smIntegrationCardAriaLabel": { + "message": "$INTEGRATION$-Implementierungsanleitung in einem neuen Tab einrichten.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Erstelle eine neue Kunden-Organisation, um sie als Anbieter zu verwalten. Zusätzliche Benutzerplätze werden im nächsten Abrechnungszeitraum berücksichtigt." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Verwalteter Dienstanbieter" }, + "managedServiceProvider": { + "message": "Verwalteter Dienstanbieter" + }, + "multiOrganizationEnterprise": { + "message": "Unternehmen mit mehreren Organisationen" + }, "orgSeats": { "message": "Benutzerplätze der Organisation" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Steuerinformationen aktualisiert" }, + "billingInvalidTaxIdError": { + "message": "Ungültige Steuer-ID. Wenn du glaubst, dass dies ein Fehler ist, wende dich bitte an den Support." + }, + "billingTaxIdTypeInferenceError": { + "message": "Wir konnten deine Steuer-ID nicht überprüfen. Wenn du glaubst, dass dies ein Fehler ist, kontaktiere bitte den Support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Ungültige Steuer-ID. Wenn du glaubst, dass dies ein Fehler ist, wende dich bitte an den Support." + }, + "billingPreviewInvoiceError": { + "message": "Bei der Vorschau der Rechnung ist ein Fehler aufgetreten. Bitte versuche es später erneut." + }, "unverified": { "message": "Nicht verifiziert" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB zusätzlicher Speicher" }, + "sshKeyAlgorithm": { + "message": "Schlüsselalgorithmus" + }, + "sshKeyFingerprint": { + "message": "Fingerabdruck" + }, + "sshKeyPrivateKey": { + "message": "Privater Schlüssel" + }, + "sshKeyPublicKey": { + "message": "Öffentlicher Schlüssel" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 Premium-Konten" }, @@ -9540,7 +9838,7 @@ "message": "Bist du sicher, dass du diesen Anhang dauerhaft löschen möchtest?" }, "manageSubscriptionFromThe": { - "message": "Abonnement verwalten von der", + "message": "Verwalte das Abonnement von", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { @@ -9549,14 +9847,14 @@ "selfHostingTitleProper": { "message": "Selbst gehostet" }, - "verified-domain-single-org-warning": { - "message": "Die Domain-Verifizierung aktiviert die Richtlinie für einzelne Organisationen." + "claim-domain-single-org-warning": { + "message": "Die Domain-Beanspruchung aktiviert die Richtlinie für einzelne Organisationen." }, "single-org-revoked-user-warning": { "message": "Nicht konforme Mitglieder werden gesperrt. Administratoren können Mitglieder wieder aufnehmen, sobald sie alle anderen Organisationen verlassen." }, "deleteOrganizationUser": { - "message": "$NAME$ gelöscht", + "message": "$NAME$ löschen", "placeholders": { "name": { "content": "$1", @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Wenn ein Mitglied gelöscht wird, werden sein Bitwarden-Konto und persönlichen Tresor-Daten dauerhaft gelöscht. Sammlungs-Daten bleiben in der Organisation. Um sie wiederherzustellen, müssen diese ein Konto erstellen und den Onboarding-Prozess erneut durchlaufen.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Dadurch werden dauerhaft alle Einträge im Besitz von $NAME$ gelöscht. Einträge aus Sammlungen sind nicht betroffen.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Dadurch werden alle Einträge der folgenden Mitglieder dauerhaft gelöscht. Einträge aus Sammlungen sind nicht betroffen.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "$NAME$ gelöscht", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Der Benutzer wurde aus der Organisation entfernt und alle zugehörigen Benutzerdaten wurden gelöscht." + }, + "deletedUserId": { + "message": "Gelöschter Benutzer $ID$ - ein Eigentümer / Administrator hat das Benutzerkonto gelöscht", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Benutzer $ID$ hat die Organisation verlassen", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ ist gesperrt", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Kontaktiere deinen Organisationseigentümer für Hilfe." + }, + "suspendedOwnerOrgMessage": { + "message": "Um wieder Zugriff zu deiner Organisation zu erhalten, füge eine Zahlungsmethode hinzu." + }, + "deleteMembers": { + "message": "Mitglieder löschen" + }, + "noSelectedMembersApplicable": { + "message": "Diese Aktion ist auf keine der ausgewählten Mitglieder anwendbar." + }, + "deletedSuccessfully": { + "message": "Erfolgreich gelöscht" + }, + "freeFamiliesSponsorship": { + "message": "Kostenloses Bitwarden Families-Sponsoring entfernen" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Erlaube Mitgliedern nicht, einen Families-Tarif über diese Organisation einzulösen." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Die Zahlung mit einem Bankkonto ist nur für Kunden in den Vereinigten Staaten möglich. Du musst dein Bankkonto verifizieren. Wir werden innerhalb der nächsten 1-2 Werktage eine Mikro-Einzahlung vornehmen. Gib den Code aus der Beschreibung dieser Einzahlung auf der Rechnungsseite der Organisation ein, um das Bankkonto zu verifizieren. Schlägt die Verifizierung des Bankkontos fehl, wird dies als versäumte Zahlung gewertet und dein Abonnement gesperrt." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Wir haben eine Mikro-Einzahlung auf dein Bankkonto vorgenommen (dies kann 1-2 Werktage dauern). Gib den sechsstelligen Code ein, der mit \"SM\" beginnt und in der Einzahlungsbeschreibung steht. Schlägt die Verifizierung des Bankkontos fehl, wird dies als versäumte Zahlung gewertet und dein Abonnement gesperrt." + }, + "descriptorCode": { + "message": "Beschreibungscode" + }, + "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" + }, + "removeMembers": { + "message": "Mitglieder entfernen" + }, + "devices": { + "message": "\"Geräte\"" + }, + "deviceListDescription": { + "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt. Wenn du ein \"Gerät\" nicht wiedererkennst, dann entferne es jetzt." + }, + "deviceListDescriptionTemp": { + "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt." + }, + "claimedDomains": { + "message": "Beanspruchte Domains" + }, + "claimDomain": { + "message": "Domain beanspruchen" + }, + "reclaimDomain": { + "message": "Domain erneut beanspruchen" + }, + "claimDomainNameInputHint": { + "message": "Beispiel: mydomain.com. Subdomains erfordern separate Einträge zur Beanspruchung." + }, + "automaticClaimedDomains": { + "message": "Automatisch beanspruchte Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden wird in den ersten 72 Stunden 3 Mal versuchen, die Domain zu beanspruchen. Wenn die Domain nicht beansprucht werden kann, überprüfe den DNS-Eintrag auf deinem Host und beanspruche sie manuell. Die Domain wird in 7 Tagen aus deiner Organisation entfernt, wenn sie nicht beansprucht ist." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ nicht beansprucht. Überprüfe deine DNS-Einträge.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Beansprucht" + }, + "domainStatusUnderVerification": { + "message": "In Verifizierung" + }, + "claimedDomainsDesc": { + "message": "Beanspruche eine Domain um alle Mitgliedskonten zu besitzen, deren E-Mail-Adresse mit der Domain übereinstimmt. Mitglieder können beim Anmelden die SSO-Kennung überspringen. Administratoren können auch Mitgliedskonten löschen." + }, + "invalidDomainNameClaimMessage": { + "message": "Die Eingabe ist kein gültiges Format. Format: mydomain.com. Subdomains erfordern separate Einträge, um beansprucht zu werden." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ beansprucht", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ nicht beansprucht", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Wenn du $EMAIL$ entfernst, kann das Sponsoring für diesen Families-Tarif nicht eingelöst werden. Bist du sicher, dass du fortfahren möchtest?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Wenn du $EMAIL$ entfernst, wird das Sponsoring für diesen Families-Tarif enden und die gespeicherte Zahlungsmethode am $DATE$ mit $40 + anfallende Steuern belastet. Du wirst bis zum $DATE$ kein neues Sponsoring einlösen können. Bist du sicher, dass du fortfahren möchtest?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain beansprucht" + }, + "organizationNameMaxLength": { + "message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten." + }, + "resellerRenewalWarning": { + "message": "Dein Abonnement wird bald verlängert. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$ um deine Verlängerung vor dem $RENEWAL_DATE$ zu bestätigen.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Eine Rechnung für dein Abonnement wurde am $ISSUED_DATE$ ausgestellt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $DUE_DATE$ zu bestätigen.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Die Rechnung für dein Abonnement wurde nicht bezahlt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $GRACE_PERIOD_END$ zu bestätigen.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organisations-Abonnement neu gestartet" + }, + "restartSubscription": { + "message": "Abonnement neu starten" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktiere $PROVIDER$ für Hilfe.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index eeb9cc14acb..adfb86665ab 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "Όλες οι εφαρμογές ($COUNT$)", "placeholders": { @@ -457,7 +469,7 @@ "message": "Δημιουργία Κωδικού" }, "generatePassphrase": { - "message": "Δημιουργία φράσης πρόσβασης" + "message": "Generate passphrase" }, "checkPassword": { "message": "Ελέγξτε εάν ο κωδικός έχει εκτεθεί." @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Ασφαλής σημείωση" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Συνδέσεις" }, @@ -718,11 +733,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Αντιγραφή φράσης πρόσβασης", + "message": "Copy passphrase", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Ο κωδικός αντιγράφηκε" + "message": "Password copied" }, "copyUsername": { "message": "Αντιγραφή ονόματος χρήστη", @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Η σύνδεση με τη χρήση συσκευής πρέπει να οριστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε κάποια άλλη επιλογή;" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Συνδεθείτε με τον κύριο κωδικό πρόσβασης" }, @@ -991,13 +1009,13 @@ "message": "Χρήση διαφορετικής μεθόδου σύνδεσης" }, "logInWithPasskey": { - "message": "Σύνδεση με κλειδί πρόσβασης" + "message": "Log in with passkey" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Καλωσορίσατε και πάλι" + "message": "Welcome back" }, "invalidPasskeyPleaseTryAgain": { "message": "Μη έγκυρο κλειδί πρόσβασης. Παρακαλώ προσπαθήστε ξανά." @@ -1099,11 +1117,23 @@ "message": "Είσοδος" }, "logInToBitwarden": { - "message": "Σύνδεση στο Bitwarden" + "message": "Log in to Bitwarden" + }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." }, "verifyIdentity": { "message": "Επαληθεύστε την ταυτότητά σας" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Η σύνδεση ξεκίνησε" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Έκδοση $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Ιστορικό Κωδικού" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Δεν υπάρχουν κωδικοί στη λίστα." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Εκκαθάριση", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Παρακαλούμε συνδεθείτε ξανά." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Παρακαλούμε συνδεθείτε ξανά. Εάν χρησιμοποιείτε άλλες εφαρμογές Bitwarden, αποσυνδεθείτε και επιστρέψτε σε αυτές επίσης." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Η Ανακληθεί η Πρόσβαση από Όλες τις Συνεδρίες" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Αυτός ο χρήστης χρησιμοποιεί τρόπο σύνδεσης δύο βημάτων για να προστατεύσει το λογαριασμό του." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Δείτε όλες τις επιλογές σύνδεσης" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Συσκευή" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Δημιουργία λογαριασμού στο" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Ενημερώστε τον Browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Χρησιμοποιείτε ένα μη υποστηριζόμενο browser. Το web vault ενδέχεται να μην λειτουργεί σωστά." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Εγγραφή στον οργανισμό" }, @@ -4388,6 +4515,9 @@ "message": "Ποτέ μην παρακινείτε να επαληθεύσετε φράσεις fingerprint για τους προσκεκλημένους χρήστες (Δεν συνιστάται)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Δωρεάν", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Συνδεθείτε χρησιμοποιώντας την πύλη μεμονωμένης σύνδεσης του οργανισμού σας. Εισάγετε το αναγνωριστικό του οργανισμού σας για να ξεκινήσετε." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Ενιαία είσοδος για επιχειρήσεις" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Εξαιρείται, δεν ισχύει για αυτήν την ενέργεια." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Αποτύπωμα" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Το Email Εστάλη" }, - "revokeSponsorshipConfirmation": { - "message": "Μετά την αφαίρεση αυτού του λογαριασμού, η χορηγία του προγράμματος «Families» θα λήξει στο τέλος της περιόδου χρέωσης. Δεν θα μπορείτε να εξαργυρώσετε μια νέα προσφορά χορηγίας μέχρι να λήξει η υπάρχουσα. Θέλετε σίγουρα να συνεχίσετε;" - }, "removeSponsorshipSuccess": { "message": "Χορηγία Αφαιρέθηκε" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Απαιτείται εάν το ID οντότητας δεν είναι URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Προαιρετικές Προσαρμογές" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Τύπος Ονόματος Χρήστη" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Ενεργοποίηση SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Η σύνδεση ξεκίνησε" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Απομνημόνευση συσκευής" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Έγκριση αιτήματος" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Απουσιάζει η διεύθυνση email του χρήστη" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "προσθέστε μια μέθοδο πληρωμής", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Πληροφορίες οργανισμού" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Χρησιμοποιήστε το SDK του Bitwarden Secrets Manager στις ακόλουθες γλώσσες προγραμματισμού για να αναπτύξετε τις δικές σας εφαρμογές." }, - "setUpGithubActions": { - "message": "Ρύθμιση Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Ρύθμιση Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Ρύθμιση GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Ρύθμιση Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "Προβολή αποθετηρίου Rust" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "Προβολή αποθετηρίου C#" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "Προβολή αποθετηρίου C++" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "Προβολή αποθετηρίου JS WebAssembly" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "Προβολή αποθετηρίου Java" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "Προβολή αποθετηρίου Python" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "Προβολή αποθετηρίου php" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "Προβολή αποθετηρίου Ruby" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "Προβολή αποθετηρίου Go" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Πάροχος διαχειριζόμενης υπηρεσίας" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Θέσεις οργανισμού" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Τα φορολογικά στοιχεία ενημερώθηκαν" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB επιπλέον χώρου αποθήκευσης" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 λογαριασμοί premium" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 00d2102c786..eacba623ecd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError":{ + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -101,6 +113,60 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -981,6 +1047,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1104,9 +1173,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1299,6 +1380,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1630,9 +1717,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1670,6 +1775,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1755,8 +1866,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3221,6 +3332,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3384,6 +3501,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3717,6 +3837,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3834,33 +4024,36 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, - "freeTrialEndPrompt": { - "message": "Your free trial ends in $COUNT$ days. To maintain your subscription,", + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", "placeholders": { "count": { "content": "$1", - "example": "You must set up 2FA on your user account before you can join this organization." + "example": "remaining days" } } }, - "freeTrialEndPromptAboveTwoDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days. To maintain your subscription,", + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", "placeholders": { "count": { "content": "$2", - "example": "organization name" + "example": "remaining days" }, "organization": { "content": "$1", - "example": "remaining days" + "example": "organization name" } } }, - "freeTrialEndPromptForOneDay": { - "message": "$ORGANIZATION$, your free trial ends tomorrow. To maintain your subscription,", + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", "placeholders": { "organization": { "content": "$1", @@ -3868,11 +4061,11 @@ } } }, - "freeTrialEndPromptForOneDayNoOrgName": { - "message": "Your free trial ends tomorrow. To maintain your subscription," + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." }, - "freeTrialEndPromptForLessThanADay": { - "message": "$ORGANIZATION$, your free trial ends today. To maintain your subscription,", + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", "placeholders": { "organization": { "content": "$1", @@ -3880,11 +4073,11 @@ } } }, - "freeTrialEndingSoonWithoutOrgName": { - "message": "Your free trial ends today. To maintain your subscription," + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." }, - "routeToPaymentMethodTrigger": { - "message": "add a payment method." + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." }, "joinOrganization": { "message": "Join organization" @@ -4440,6 +4633,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4670,6 +4866,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5574,6 +5776,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle":{ + "message": "Non-compliant members" + }, + "nonCompliantMembersError":{ + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5589,6 +5797,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6081,9 +6303,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6373,6 +6592,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6465,8 +6687,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6479,6 +6701,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6733,6 +6975,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -8010,9 +8256,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8137,6 +8392,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8242,6 +8509,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8337,9 +8607,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8918,44 +9185,103 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "singleSignOn": { + "message": "Single sign-on" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpAnsible": { - "message": "Set up Ansible" + "userProvisioning":{ + "message": "User provisioning" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegration": { + "message": "SCIM" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdc":{ + "message": "Bitwarden Directory Connector" }, - "javaSDKRepo": { - "message": "View Java repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagement":{ + "message": "Event management" }, - "phpSDKRepo": { - "message": "View php repository" + "eventManagementDesc":{ + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagement":{ + "message": "Device management" }, - "goSDKRepo": { - "message": "View Go repository" + "deviceManagementDesc":{ + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + + }, + "integrationCardTooltip":{ + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip":{ + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip":{ + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel":{ + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel":{ + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel":{ + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9117,6 +9443,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9631,8 +9969,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning" : { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning" : { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9647,9 +9985,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9695,5 +10043,220 @@ }, "suspendedOwnerOrgMessage": { "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 7fac11d847c..a2002039ab9 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritise security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organisation access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -69,7 +81,7 @@ "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "You haven't marked any applications as Critical" }, "noCriticalAppsDescription": { "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1270,7 +1300,7 @@ "message": "You do not have permission to view all items in this collection." }, "youDoNotHavePermissions": { - "message": "You do not have permissions for this collection" + "message": "You do not have permissions to this collection" }, "noCollectionsInList": { "message": "There are no collections to list." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorised" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organisation" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organisation's single sign-on portal. Please enter your organisation's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organisation's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organisation's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organisation or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organisation administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customisations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behaviour for the organisation" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisation information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organisation to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organisation enterprise" + }, "orgSeats": { "message": "Organisation Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organisation policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organisation policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organisations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organisation. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organisation and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organisation", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organisation owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organisation, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organisation." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organisation's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organisation in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organisation name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 96d0e5403bf..2564812802a 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritise security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organisation access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -69,7 +81,7 @@ "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "You haven't marked any applications as Critical" }, "noCriticalAppsDescription": { "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1270,7 +1300,7 @@ "message": "You do not have permission to view all items in this collection." }, "youDoNotHavePermissions": { - "message": "You do not have permissions for this collection" + "message": "You do not have permissions to this collection" }, "noCollectionsInList": { "message": "There are no collections to list." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorised" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organisation" }, @@ -4388,6 +4515,9 @@ "message": "Don't ask to verify fingerprint phrase again", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organisation's single sign-on portal. Please enter your organisation's identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organisation's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organisation's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organisation or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organisation administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customisations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behaviour for the organisation" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisation information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organisation to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organisation enterprise" + }, "orgSeats": { "message": "Organisation Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid Aadhaar ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your Aadhaar ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid Aadhaar ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9544,13 +9842,13 @@ "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your licence file. To support Free Families plans and advanced billing capabilities for your self-hosted organisation, you will need to set up automatic sync in your self-hosted organisation." + "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organisation, you will need to set up automatic sync in your self-hosted organisation." }, "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organisation policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organisation policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organisations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organisation. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organisation and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organisation", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organisation owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organisation, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organisation." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organisation's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organisation in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organisation name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index ccc026e2120..21eba5a4a99 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1,18 +1,21 @@ { "allApplications": { - "message": "All applications" + "message": "Ĉiuj aplikaĵoj" }, "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,8 +29,17 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Ĉiuj aplikaĵoj ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -404,7 +416,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "Vi", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Sekura noto" }, + "typeSshKey": { + "message": "SSH-ŝlosilo" + }, "typeLoginPlural": { "message": "Salutoj" }, @@ -590,7 +605,7 @@ "message": "Plena Nomo" }, "address": { - "message": "Address" + "message": "Adreso" }, "address1": { "message": "Adreso 1" @@ -662,7 +677,7 @@ } }, "new": { - "message": "New", + "message": "Nova", "description": "for adding new items" }, "item": { @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Saluti kun la ĉefpasvorto" }, @@ -1099,11 +1117,23 @@ "message": "Saluti" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Ensaluti en Bitwarden" + }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1230,10 +1260,10 @@ "message": "Retpoŝta Adreso" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Via trezorejo estas ŝlosita" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Via konto estas ŝlosita" }, "uuid": { "message": "UUID" @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versio $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Pasvorta Historio" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Ne estas listigitaj pasvortoj." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Malplenigi", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Bonvolu saluti refoje." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Bonvolu saluti refoje. Se vi uzas aliajn programojn de Bitwarden, adiaŭu kaj ankaŭ salutu ilin." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Ĉiuj Sesioj Neaŭtorizitaj" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ĉi tiu uzanto uzas du-paŝan ensaluton por protekti sian konton." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Aparato" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Ĝisdatigi retumilon" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Vi uzas nesubtenatan tTT-legilon. La ttt-volbo eble ne funkcias ĝuste." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Aliĝi al Organizo" }, @@ -4388,6 +4515,9 @@ "message": "Ne petu kontroli denove fingrospuran frazon", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Senpaga", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Ensalutu per la unika ensaluta portalo de via organizo. Bonvolu enigi la identigilon de via organizo por komenci." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Entreprena Ununura Ensaluto" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Retmesaĝo Sendiĝis" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index c053473ac9d..1393fe59b27 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1,18 +1,21 @@ { "allApplications": { - "message": "All applications" + "message": "Todas las aplicaciones" }, "criticalApplications": { - "message": "Critical applications" + "message": "Aplicaciones críticas" + }, + "accessIntelligence": { + "message": "Access Intelligence" }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { - "message": "Password Risk" + "message": "Riesgo de contraseña" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -24,10 +27,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Miembros notificados" + }, + "revokeMembers": { + "message": "Revocar miembros" + }, + "restoreMembers": { + "message": "Restaurar miembros" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Todas las aplicaciones ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -39,7 +51,7 @@ "message": "Create new login item" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Aplicaciones críticas ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,7 +60,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Miembros notificados ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -84,7 +96,7 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "Aplicación" }, "atRiskPasswords": { "message": "At-risk passwords" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Inicios de sesión" }, @@ -753,31 +768,31 @@ "message": "Copy website" }, "copyNotes": { - "message": "Copy notes" + "message": "Copiar notas" }, "copyAddress": { - "message": "Copy address" + "message": "Copiar dirección" }, "copyPhone": { - "message": "Copy phone" + "message": "Copiar teléfono" }, "copyEmail": { - "message": "Copy email" + "message": "Copiar correo electrónico" }, "copyCompany": { "message": "Copy company" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Copiar número de seguro social" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copiar número de pasaporte" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copiar número de licencia" }, "copyName": { - "message": "Copy name" + "message": "Copiar nombre" }, "me": { "message": "Yo" @@ -946,7 +961,7 @@ "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Enlace expirado" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Iniciar sesión con el dispositivo debe configurarse en los ajustes de la aplicación Bitwarden. ¿Necesitas otra opción?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Iniciar sesión con contraseña maestra" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verifica tu identidad" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Inicio de sesión en proceso" }, @@ -1160,10 +1190,10 @@ "message": "Configuración" }, "accountEmail": { - "message": "Account email" + "message": "Correo electrónico de la cuenta" }, "requestHint": { - "message": "Request hint" + "message": "Solicitar pista" }, "requestPasswordHint": { "message": "Request password hint" @@ -1212,7 +1242,7 @@ "message": "Your new account has been created!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "¡Has iniciado sesión!" }, "trialAccountCreated": { "message": "Cuenta creada con éxito." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versión $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Historial de contraseñas" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "No hay contraseñas que listar." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Limpiar", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Por favor, vuelve a acceder." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, vuelve a acceder. Si estás utilizando otras aplicaciones de Bitwarden, cierra sesión y vuelva a acceder en ellas también." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Desautorizadas todas las sesiones" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Este usuario está usando autenticación de dos pasos para proteger su cuenta." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Ver todas las opciones de inicio de sesión" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creando una cuenta en" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Actualizar navegador" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Está utilizando un navegador web no compatible. Es posible que la caja fuerte web no funcione correctamente." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Únete a la organización" }, @@ -4388,6 +4515,9 @@ "message": "No pida verificar la frase de la huella dactilar de nuevo", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Inicie sesión utilizando el portal de inicio de sesión único de su organización. Introduzca el identificador de su organización para comenzar." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Inicio de sesión único empresarial" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluido, no aplicable a esta acción." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Huella digital" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "El permiso \"gestionar usuarios\" también debe ser otorgado junto con el permiso \"gestionar cuenta\"" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Correo electrónico enviado" }, - "revokeSponsorshipConfirmation": { - "message": "Después de eliminar esta cuenta, el propietario de la organización familiar será responsable de esta suscripción y de las facturas relacionadas. ¿Está seguro de que desea continuar?" - }, "removeSponsorshipSuccess": { "message": "Patrocinio eliminado" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Requerido si el ID de la entidad no es una URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Personalizaciones opcionales" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo de nombre de usuario" }, @@ -6681,6 +6857,10 @@ "message": "Provisionar automáticamente a los usuarios y grupos con su proveedor de identidad preferido a través de la provisión SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Activar SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7660,7 +7840,7 @@ "message": "Subir archivo" }, "upload": { - "message": "Upload" + "message": "Subir" }, "acceptedFormats": { "message": "Formatos aceptados:" @@ -7672,13 +7852,13 @@ "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloquear con biométricos" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Desbloquear con PIN" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloquear con contraseña maestra" }, "licenseAndBillingManagement": { "message": "Gestión de licencias y facturación" @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Inicio de sesión en proceso" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Se requiere aprobación en el dispositivo. Seleccione una opción de aprobación a continuación:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Recordar en este dispositivo" }, @@ -8037,7 +8226,7 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "Verificación requerida", "description": "Default title for the user verification dialog." }, "recoverAccount": { @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Aprobar solicitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hay solicitudes de dispositivo" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Falta el correo electrónico del usuario" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositivo de confianza" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Administrar el comportamiento de la colección para la organización" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8426,7 +8627,7 @@ "message": "Service account access updated" }, "commonImportFormats": { - "message": "Common formats", + "message": "Formatos comunes", "description": "Label indicating the most common import formats" }, "maintainYourSubscription": { @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Información de la Organización" @@ -8484,7 +8685,7 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooExpensive": { - "message": "Too expensive", + "message": "Demasiado caro", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { @@ -8549,16 +8750,16 @@ } }, "addField": { - "message": "Add field" + "message": "Añadir campo" }, "editField": { - "message": "Edit field" + "message": "Editar campo" }, "items": { "message": "Elementos" }, "assignedSeats": { - "message": "Assigned seats" + "message": "Asientos asignados" }, "assigned": { "message": "Asignado" @@ -8573,16 +8774,16 @@ "message": "Desvincular organización" }, "manageSeats": { - "message": "MANAGE SEATS" + "message": "GESTIONAR ASIENTOS" }, "manageSeatsDescription": { "message": "Adjustments to seats will be reflected in the next billing cycle." }, "unassignedSeatsDescription": { - "message": "Unassigned seats" + "message": "Asientos no asignados" }, "purchaseSeatDescription": { - "message": "Additional seats purchased" + "message": "Asientos adicionales comprados" }, "assignedSeatCannotUpdate": { "message": "Assigned Seats can not be updated. Please contact your organization owner for assistance." @@ -8591,7 +8792,7 @@ "message": "Subscription update failed" }, "trial": { - "message": "Trial", + "message": "Prueba", "description": "A subscription status label." }, "pastDue": { @@ -8639,7 +8840,7 @@ "description": "The body of a warning box shown to a user whose subscription is unpaid." }, "cancellationDate": { - "message": "Cancellation date", + "message": "Fecha de cancelación", "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { @@ -8809,7 +9010,7 @@ "message": "You cannot add yourself to a group." }, "deleteProvider": { - "message": "Delete provider" + "message": "Eliminar proveedor" }, "deleteProviderConfirmation": { "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." @@ -8855,7 +9056,7 @@ "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integraciones" }, "integrationsDesc": { "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configurar", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configurar ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -8912,7 +9168,7 @@ "message": "Select a plan" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "Descuento del 35%" }, "monthPerMember": { "message": "month per member" @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 6ba79ebe6c1..9122e4b8778 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Turvaline märkus" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Kontod" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Bitwardeni rakenduse seadistuses peab olema konfigureeritud sisselogimine läbi seadme. Soovid teist valikut?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Logi sisse ülemparooliga" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Kinnitage oma Identiteet" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Sisselogimine käivitatud" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versioon $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Paroolide ajalugu" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Puuduvad paroolid, mida kuvada." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Tühjenda", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Palun logi uuesti sisse." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Palun logi uuesti sisse. Kui kasutad teisi Bitwardeni rakendusi, pead ka nendes uuesti sisse logima." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Kõikidest seadmetest on välja logitud" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Sellel kasutajal on kaheastmeline kinnitamine sisse lülitatud." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Vaata kõiki sisselogimise valikuid" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Seade" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Konto loomise asukoht" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Uuenda brauserit" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Kasutad brauserit, mida ei toetata. Veebihoidla ei pruugi hästi töötada." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Liitu organisatsiooniga" }, @@ -4388,6 +4515,9 @@ "message": "Ära enam küsi kutsutud kasutajate unikaalse sõnajada kinnitamist (mittesoovitatav)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Tasuta", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Sisselogimine läbi organisatsiooni ühekordse sisselogimise portaali. Jätkamiseks sisesta ettevõtte identifikaator." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Ettevõtte Single Sign-On" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Välja jäetud, ei rakendu sellel tegevuse puhul." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Sõrmejälg" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-kiri on saadetud" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Kasutajanime tüüp" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Kasuta Bitwarden Secrets Manageri SDK järgnevates programmeerimiskeeltes, millega saad ehitada enda rakendusi." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 9f67ef96101..576d8207aae 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Ohar segurua" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Saio hasierak" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Hasi saioa pasahitz nagusiarekin" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Saioa hastea martxan da" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Jakinarazpen bat bidali da zure gailura." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "$VERSION_NUMBER$ bertsioa", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Pasahitz historia" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Ez dago erakusteko pasahitzik." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Ezabatu", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Mesedez, hasi saioa berriro." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mesedez, hasi saioa berriro. Bitwardenen beste aplikazioren bat erabiltzen ari bazara, itxi saioa eta hasi saioa berriro aplikazio horretan ere." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Saio guztiei baimena kendua" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Erabiltzaile hau bi urratseko saio hasiera erabiltzen ari da bere kontua babesteko." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Ikusi erregistro guztiak ezarpenetan" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Gailua" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Nabigatzailea eguneratu" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Euskarririk gabeko web nabigatzailea erabiltzen ari zara. Baliteke webguneko kutxa gotorrak behar bezala ez funtzionatzea." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Erakundeko kide egin" }, @@ -4388,6 +4515,9 @@ "message": "Inoiz ez da beharrezkoa gonbidatutako erabiltzaileentzat hatz-marka esaldiak egiaztatzea (ez da gomendatzen)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Doan", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Hasi saioa zure erakundeko atari bakarra erabiliz. Sartu zure erakundearen identifikatzailea hasteko." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enpresentzako saio hasiera bakarra" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Baztertua, ez dagokio ekintza honi." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Hatz-marka" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Emaila bidalia" }, - "revokeSponsorshipConfirmation": { - "message": "Kontu hau ezabatu ondoren, fakturazio-aldiaren amaieran amaituko da Familiak planaren babesa. Ezin izango duzu beste babes-eskaintza bat trukatu, harik eta eskaintza amaitu arte. Ziur al daude jarraitu nahi duzula?" - }, "removeSponsorshipSuccess": { "message": "Babeslea kendua" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Beharrezkoa da entitatearen ID-a URL bat ez bada." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Aukerako pertsonalizazioa" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Erabiltzaile izen mota" }, @@ -6681,6 +6857,10 @@ "message": "Eman automatikoki erabiltzaileei eta taldeei zure identitate-hornitzaile gogokoenarekin, SCIM hornitzaile bidez", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM gaituta", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index e4903ab83a3..589a019973f 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "یادداشت امن" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "ورودها" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "با کلمه عبور اصلی وارد شوید" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ورود به سیستم آغاز شد" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "نسخه $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "تاریخچه کلمه عبور" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "پاک کردن", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "لطفاً دوباره وارد شوید." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "همه نشست‌ها غیرمجاز است" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "مشاهده همه گزینه‌های ورود به سیستم" }, @@ -3714,6 +3780,15 @@ "device": { "message": "دستگاه" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "به‌روزرسانی مرورگر" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "شما از یک مرورگر وب پشتیبانی نشده استفاده می‌کنید. گاوصندوق وب ممکن است به درستی کار نکند." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "به سازمان بپیوندید" }, @@ -4388,6 +4515,9 @@ "message": "هرگز تأیید عبارات اثر انگشت را برای کاربران دعوت شده درخواست نکنید (توصیه نمی‌شود)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "رايگان", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "با استفاده از پورتال ورود واحد سازمان خود وارد شوید. لطفاً برای شروع، شناسه SSO سازمان خود را وارد کنید." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "ورود به سیستم پروژه" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "مستثنی شده، برای این اقدام قابل اجرا نیست" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "اثر انگشت" }, @@ -5537,6 +5679,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "مدیر کاربران همچنین باید مجوز مدیریت بازیابی حساب کاربری را داشته باشد" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "ایمیل ارسال شد" }, - "revokeSponsorshipConfirmation": { - "message": "پس از حذف این حساب، حمایت مالی طرح خانواده در پایان دوره صورت‌حساب منقضی می‌شود. تا زمانی که پیشنهاد حمایتی موجود منقضی نشده باشد، نمی‌توانید از پیشنهاد حمایت مالی جدید استفاده کنید. آیا مطمئنید که می‌خواهید ادامه دهید؟" - }, "removeSponsorshipSuccess": { "message": "حمایت مالی حذف شد" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "اگر شناسه موجودیت یک نشانی اینترنتی نباشد، الزامی است." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "سفارشی سازی اختیاری" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "نوع نام کاربری" }, @@ -6681,6 +6857,10 @@ "message": "به‌طور خودکار کاربران و گروه‌ها را با ارائه‌دهنده هویت ترجیحی خود از طریق تأمین SCIM فراهم کنید", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM را فعال کنید", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "درخواست تأیید" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "بدون درخواست دستگاه" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "ایمیل کاربر وجود ندارد" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 69d53ee762d..b6825bed162 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kriittiset sovellukset" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Salasanariski" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Tiedot päivitetty viimeksi: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Ilmoitetut jäsenet" }, + "revokeMembers": { + "message": "Kumoa jäsenet" + }, + "restoreMembers": { + "message": "Palauta jäsenet" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "Kaikki sovellukset ($COUNT$)", "placeholders": { @@ -66,13 +78,13 @@ } }, "noAppsInOrgDescription": { - "message": "Kun käyttäjät tallentavat kirjautumistietoja, näytetään sovellukset ja vaarantuneet salasanat täällä. Merkitse kriittiset sovellukset ja ilmoita käyttäjille salasanojen vaihdosta." + "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." }, "noCriticalAppsTitle": { - "message": "Sovelluksia ei ole merkitty kriittisiksi" + "message": "Et ole merkinnyt yhtään sovellusta Kriittiseksi" }, "noCriticalAppsDescription": { - "message": "Valitse kriittisimmät sovelluksesi havaitaksesi vaarantuneet salasanat ja ilmoita käyttäjille niiden vaihdosta." + "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." }, "markCriticalApps": { "message": "Merkitse kriittiset sovellukset" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Salattu muistio" }, + "typeSshKey": { + "message": "SSH-avain" + }, "typeLoginPlural": { "message": "Kirjautumistiedot" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Laitteella kirjautuminen on määritettävä Bitwarden-sovelluksen asetuksista. Tarvitsetko eri vaihtoehdon?" }, + "needAnotherOptionV1": { + "message": "Tarvitsetko toisen vaihtoehdon?" + }, "loginWithMasterPassword": { "message": "Kirjaudu pääsalasanalla" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Kirjaudu Bitwardeniin" }, + "authenticationTimeout": { + "message": "Todennuksen aikakatkaisu" + }, + "authenticationSessionTimedOut": { + "message": "Todennusistunto aikakatkaistiin. Ole hyvä ja aloita kirjautumisprosessi uudelleen." + }, "verifyIdentity": { "message": "Vahvista henkilöllisyytesi" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Kirjautuminen aloitettu" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Laitteellesi on lähetetty ilmoitus." }, + "aNotificationWasSentToYourDevice": { + "message": "Laitteeseesi lähetettiin ilmoitus" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versio $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Salasanahistoria" }, + "generatorHistory": { + "message": "Generaattorihistoria" + }, + "clearGeneratorHistoryTitle": { + "message": "Tyhjennä generaattorihistoria" + }, + "cleargGeneratorHistoryDescription": { + "message": "Jos jatkat, kaikki generaattorihistorian kohteet poistetaan. Haluatko varmasti jatkaa?" + }, "noPasswordsInList": { "message": "Näytettäviä salasanoja ei ole." }, + "clearHistory": { + "message": "Tyhjennä historia" + }, + "nothingToShow": { + "message": "Ei näytettävää" + }, + "nothingGeneratedRecently": { + "message": "Et ole luonut mitään hiljattain" + }, "clear": { "message": "Tyhjennä", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Kirjaudu sisään uudelleen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Kirjaudu uudelleen sisään. Jos käytät muita Bitwarden-sovelluksia, kirjaudu myös niihin uudelleen." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Kaikki istunnot mitätöitiin" }, - "accountIsManagedMessage": { - "message": "Tiliä hallinnoi $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Tämän tilin omistaa $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Käyttäjä on suojannut tilinsä kaksivaiheisella kirjautumisella." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Näytä kaikki kirjautumisvaihtoehdot" + }, "viewAllLoginOptions": { "message": "Näytä kaikki kirjautumisvaihtoehdot" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Laite" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Luodaan tili palvelimelle" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Päivitä selain" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Käytät selainta, jota ei tueta. Verkkoholvi ei välttämättä toimi oikein." }, + "freeTrialEndPromptCount": { + "message": "Ilmainen kokeilujakso päättyy $COUNT$ päivän kuluttua.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, ilmainen kokeilujakso päättyy $COUNT$ päivän kuluttua.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, ilmainen kokeilujaksosi päättyy huomenna.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Ilmainen kokeilujaksosi päättyy huomenna." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, ilmainen kokeilujaksosi päättyy tänään.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Ilmainen kokeilujaksosi päättyy tänään." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Liity organisaatioon" }, @@ -4388,6 +4515,9 @@ "message": "Älä koskaan kehota vahvistamaan kutsuttujen käyttäjien tunnistelausekkeita (ei suositella)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Ilmoitamme sinulle kun pyyntösi on hyväksytty" + }, "free": { "message": "Ilmainen", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Kirjaudu organisaatiosi kertakirjautumisportaalista. Aloita syöttämällä organisaatiosi kertakirjautumistunniste." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Yrityksen kertakirjautuminen" }, @@ -4686,7 +4822,7 @@ "message": "Estä jäseniä liittymästä muihin organisaatioihin." }, "singleOrgPolicyDesc": { - "message": "Estä jäseniä liittymästä muihin organisaatioihin. Tätä käytäntöä tarvitaan organisaatioille, jotka käyttävät verkkotunnustodennusta." + "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." }, "singleOrgBlockCreateMessage": { "message": "Nykyisen organisaatiosi käytäntö ei salli liittymistä yhtä useampaan organisaatioon. Ole yhteydessä organisaatiosi ylläpitoon tai liity eri Bitwarden-tilin kautta." @@ -4695,7 +4831,7 @@ "message": "Organisaation jäsenet, jotka eivät ole omistajia tai ylläpitäjiä ja jotka ovat jo toisen organisaation jäseniä, poistetaan organisaatiostasi." }, "singleOrgPolicyMemberWarning": { - "message": "Käytännöstä poikkeavat jäsenet asetetaan mitätöityyn tilaan, kunnes he poistuvat kaikista muista organisaatioista. Poikkeuksena ylläpitäjät, jotka voivat palauttaa jäsenten tilan vaatimusten täytyttyä." + "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." }, "requireSso": { "message": "Vaadi todennus kertakirjautumisella" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Ohitettu, ei koske tätä toimintoa" }, + "nonCompliantMembersTitle": { + "message": "Jäsenet jotka eivät täytä vaatimuksia" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Sormenjälki" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Tilien palautusavun hallinta\" -oikeuden kanssa on myönnettävä myös \"Käyttäjien hallinta\" -oikeus." }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Sähköposti lähetettiin" }, - "revokeSponsorshipConfirmation": { - "message": "Tilin poiston jälkeen Families-tilauksen sponsorointi päättyy kuluvan laskutusjakson lopussa. Et voi lunastaa uuttaa sponsorointitarjousta ennen nykyisen päättymistä. Haluatko varmasti jatkaa?" - }, "removeSponsorshipSuccess": { "message": "Sponsorointi poistettiin" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Vaaditaan, jos Entity ID ei ole kelvollinen URL-osoite." }, + "offerNoLongerValid": { + "message": "Tämä tarjous ei ole enää voimassa. Ota yhteyttä organisaation ylläpitäjiin saadaksesi lisätietoja." + }, "openIdOptionalCustomizations": { "message": "Valinnaiset mukautukset" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Luo sähköpostiosoite" }, - "generatorBoundariesHint": { - "message": "Arvon tulee olla väliltä $MIN$ - $MAX$", + "spinboxBoundariesHint": { + "message": "Arvon tulee olla väliltä $MIN$—$MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Käytä $RECOMMENDED$ tai useampaa merkkiä vahvan salasanan luomiseksi.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Käytä $RECOMMENDED$ tai useampaa sanaa vahvan salalauseen luomiseksi.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Käyttäjätunnuksen tyyppi" }, @@ -6681,6 +6857,10 @@ "message": "Provisioi käyttäjät ja ryhmät automaattisesti haluamasi identiteettitoimittajan kanssa SCIM-provisioinnilla", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Ota SCIM käyttöön", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Kirjautuminen aloitettu" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Muista tämä laite tehdäksesi tulevista kirjautumisista helpompaa" + }, "deviceApprovalRequired": { "message": "Laitehyväksyntä vaaditaan. Valitse hyväksyntätapa alta:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Muista tämä laite" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Hyväksy pyyntö" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Laitepyyntöjä ei ole" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Käyttäjän sähköpostiosoite puuttuu" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktiivista käyttäjän sähköpostiosoitetta ei löytynyt. Kirjaudutaan ulos." + }, "deviceTrusted": { "message": "Laitteeseen luotettu" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Voi hallita organisaation kokoelmien toimintaa." }, - "limitCollectionCreationDeletionDesc": { - "message": "Rajoita kokoelmien luonti ja poisto omistajille ja ylläpitäjille" - }, "limitCollectionCreationDesc": { "message": "Rajoita kokoelmien luonti omistajille ja ylläpitäjille" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "lisää maksutapa", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisaation tiedot" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Bitwardenin Salaisuushallinnan SDK:n avulla voit kehittää omia sovelluksiasi seuraavilla ohjelmointikielillä." }, - "setUpGithubActions": { - "message": "Määritä GitHub Actions" + "ssoDescStart": { + "message": "Konfiguroi", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM-käyttäjähallinta" + }, + "scimIntegrationDescStart": { + "message": "Konfiguroi ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Määritä Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Määritä GitLab CI/CD" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Määritä Ansible" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "rustSDKRepo": { - "message": "Näytä Rust-arkisto" + "eventManagement": { + "message": "Tapahtumahallinta" }, - "cSharpSDKRepo": { - "message": "Näytä C#-arkisto" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "cPlusPlusSDKRepo": { - "message": "Näytä C++-arkisto" + "deviceManagement": { + "message": "Laitehallinta" }, - "jsWebAssemblySDKRepo": { - "message": "Näytä JS WebAssembly -arkisto" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "javaSDKRepo": { - "message": "Näytä Java-arkisto" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Määritä $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Näytä Python-arkisto" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Näytä php-arkisto" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Näytä Ruby-arkisto" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Näytä Go-arkisto" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Luo uusi asiakasorganisaatio, jota hallitset toimittajana. Uudet käyttäjäpaikat näkyvät seuraavalla laskutusjaksolla." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Hallintapalvelun toimittaja" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organisaation käyttäjäpaikat" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Verotiedot muutettiin" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Vahvistamaton" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "Gt enemmän tallennustilaa" }, + "sshKeyAlgorithm": { + "message": "Avainalgoritmi" + }, + "sshKeyFingerprint": { + "message": "Sormenjälki" + }, + "sshKeyPrivateKey": { + "message": "Yksityinen avain" + }, + "sshKeyPublicKey": { + "message": "Julkinen avain" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "Kuusi premium-tiliä" }, @@ -9540,7 +9838,7 @@ "message": "Haluatko varmasti poistaa liitteen pysyvästi?" }, "manageSubscriptionFromThe": { - "message": "Hallitse tilausta sivulta", + "message": "Manage subscription from the", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { @@ -9549,14 +9847,14 @@ "selfHostingTitleProper": { "message": "Itse ylläpidetty" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Poista $NAME$", "placeholders": { "name": { "content": "$1", @@ -9565,12 +9863,22 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ poistettiin", "placeholders": { "name": { "content": "$1", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Käyttäjä $ID$ jätti organisaation", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Pyydä organisaation omistajalta apua." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Poista jäsenet" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Poisto onnistui" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "importantNotice": { + "message": "Tärkeä ilmoitus" + }, + "setupTwoStepLogin": { + "message": "Määritä kaksivaiheinen kirjautuminen" + }, + "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": "Ota kaksivaiheinen kirjautuminen käyttöön" + }, + "changeAcctEmail": { + "message": "Muuta tilin sähköpostiosoitetta" + }, + "removeMembers": { + "message": "Poista jäsenet" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 7b880a85b52..9dcacdb44f8 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure na tala" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Mga Login" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Mag-log in gamit ang master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Sinimulan ang pag-log in" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Nakapagpadala na ng notipikasyon sa device mo." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Bersyon $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Kasaysayan ng password" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Walang maililistang password." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Burahin", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Mangyaring mag-log in ulit." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mangyaring mag-log in ulit. Kung gumagamit ka ng iba pang mga aplikasyon pang-Bitwarden, mag-log out at log in rin ulit sa mga iyon." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Na-deauthorize lahat ng mga sesyon" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Gumagamit ang user na ito ng dalawang hakbang na pag login upang maprotektahan ang kanilang account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Tingnan ang lahat ng mga pagpipilian sa pag log in" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update sa browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Gumagamit ka ng isang hindi suportado na web browser. Ang web vault ay maaaring hindi gumana nang maayos." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Sumali sa organisasyon" }, @@ -4388,6 +4515,9 @@ "message": "Huwag kailanman mag prompt upang i verify ang mga parirala ng fingerprint para sa mga inimbitahang gumagamit (hindi inirerekomenda)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Libre", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Mag log in gamit ang single sign on portal ng iyong samahan. Ipasok lamang ang SSO identifier ng iyong organisasyon upang magsimula." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise Single Sign-On sa Filipino ay Isang Sign-On na Enterprise" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Hindi kasama, hindi naaangkop para sa pagkilos na ito" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Ipinadala ang email" }, - "revokeSponsorshipConfirmation": { - "message": "Matapos alisin ang account na ito, ang sponsorship ng plano ng mga Pamilya ay mawawalan ng bisa sa pagtatapos ng panahon ng pagsingil. Hindi ka makakapag redeem ng bagong sponsorship offer hanggang sa mag expire ang existing. Sigurado ka bang gusto mong magpatuloy?" - }, "removeSponsorshipSuccess": { "message": "Inalis ang sponsorship" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Kinakailangan kung ang Entity ID ay hindi isang URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Opsyonal na mga pagpapasadya" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Uri ng username" }, @@ -6681,6 +6857,10 @@ "message": "Awtomatikong pagbibigay ng mga user at grupo sa iyong ginustong identity provider sa pamamagitan ng SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Paganahin ang SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "rustSDKRepo": { - "message": "View Rust repository" + "eventManagement": { + "message": "Event management" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "deviceManagement": { + "message": "Device management" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "javaSDKRepo": { - "message": "View Java repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 270c93b2c5f..933bfd8759f 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Applications critiques" }, + "accessIntelligence": { + "message": "Accéder à Intelligence" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Aperçus des Risques" }, "passwordRisk": { "message": "Risque du mot de passe" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Examinez les mots de passe à risque (faibles, exposés ou réutilisés) à travers les applications. Sélectionnez vos applications les plus critiques pour prioriser les actions de sécurité pour que vos utilisateurs s'occupent des mots de passe à risque." }, "dataLastUpdated": { "message": "Dernière mise à jour des données : $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Membres notifiés" }, + "revokeMembers": { + "message": "Révoquer des membres" + }, + "restoreMembers": { + "message": "Restaurer des membres" + }, + "cannotRestoreAccessError": { + "message": "Impossible de restaurer l'accès à l'organisation" + }, "allApplicationsWithCount": { "message": "Toutes les applications ($COUNT$)", "placeholders": { @@ -57,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "Aucune application trouvée dans $ORG NOM$", + "message": "Aucune application trouvée dans $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Note sécurisée" }, + "typeSshKey": { + "message": "Clé SSH" + }, "typeLoginPlural": { "message": "Identifiants" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "La connexion avec l'appareil doit être configurée dans les paramètres de l'application Bitwarden. Besoin d'une autre option ?" }, + "needAnotherOptionV1": { + "message": "Besoin d'une autre option ?" + }, "loginWithMasterPassword": { "message": "Se connecter avec le mot de passe principal" }, @@ -997,7 +1015,7 @@ "message": "Utiliser l'authentification unique" }, "welcomeBack": { - "message": "Bon retour" + "message": "Content de vous revoir" }, "invalidPasskeyPleaseTryAgain": { "message": "Passkey invalide. Veuillez réessayer de nouveau." @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Se connecter à Bitwarden" }, + "authenticationTimeout": { + "message": "Délai d'authentification dépassé" + }, + "authenticationSessionTimedOut": { + "message": "La session d'authentification a expiré. Veuillez redémarrer le processus de connexion." + }, "verifyIdentity": { "message": "Vérifiez votre Identité" }, + "whatIsADevice": { + "message": "Qu'est-ce qu'un appareil ?" + }, + "aDeviceIs": { + "message": "Un appareil est une installation unique de l'application Bitwarden où vous vous êtes connecté. Réinstaller, effacer les données de l'application ou effacer vos cookies peut entraîner l'apparition d'un appareil plusieurs fois." + }, "logInInitiated": { "message": "Connexion initiée" }, @@ -1270,7 +1300,7 @@ "message": "Vous n'avez pas la permission de voir tous les éléments de cette collection." }, "youDoNotHavePermissions": { - "message": "Vous n'avez pas les permissions pour cette collection" + "message": "Vous n'avez pas les autorisations pour cette collection" }, "noCollectionsInList": { "message": "Aucune collection à afficher." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Une notification a été envoyée à votre appareil." }, + "aNotificationWasSentToYourDevice": { + "message": "Une notification a été envoyée à votre appareil" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte correspond à l'autre appareil" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Historique des mots de passe" }, + "generatorHistory": { + "message": "Historique du générateur" + }, + "clearGeneratorHistoryTitle": { + "message": "Effacer l'historique du générateur" + }, + "cleargGeneratorHistoryDescription": { + "message": "Si vous continuez, toutes les entrées seront définitivement supprimées de l'historique du générateur. Êtes-vous sûr de vouloir continuer?" + }, "noPasswordsInList": { "message": "Aucun mot de passe à afficher." }, + "clearHistory": { + "message": "Effacer l'historique" + }, + "nothingToShow": { + "message": "Rien à afficher" + }, + "nothingGeneratedRecently": { + "message": "Vous n'avez rien généré récemment" + }, "clear": { "message": "Effacer", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Veuillez vous reconnecter." }, + "currentSession": { + "message": "Session en cours" + }, + "requestPending": { + "message": "Demande en attente" + }, "logBackInOthersToo": { "message": "Veuillez vous reconnecter. Si vous utilisez d'autres applications Bitwarden, déconnectez-vous et reconnectez-vous également de celles-ci." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Toutes les sessions ont été révoquées" }, - "accountIsManagedMessage": { - "message": "Ce compte est géré par $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Ce compte appartient à $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Cet utilisateur utilise l'authentification à deux facteurs pour protéger son compte." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Afficher toutes les options de connexion" + }, "viewAllLoginOptions": { "message": "Voir toutes les options de connexion" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Appareil" }, + "loginStatus": { + "message": "Statut de connexion" + }, + "firstLogin": { + "message": "Première connexion" + }, + "trusted": { + "message": "Approuvé" + }, "creatingAccountOn": { "message": "Création du compte sur" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Mettre à jour le navigateur" }, + "generatingRiskInsights": { + "message": "Génération de vos connaissances en matière de risque..." + }, "updateBrowserDesc": { "message": "Vous utilisez un navigateur non supporté. Le coffre web pourrait ne pas fonctionner correctement." }, + "freeTrialEndPromptCount": { + "message": "Votre essai gratuit se termine dans $COUNT$ jours.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, votre essai gratuit se termine dans $COUNT$ jours.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, votre essai gratuit se termine demain.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Votre essai gratuit se termine demain." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, votre essai gratuit se termine aujourd'hui.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Votre essai gratuit se termine aujourd'hui." + }, + "clickHereToAddPaymentMethod": { + "message": "Cliquer ici pour ajouter une méthode de paiement." + }, "joinOrganization": { "message": "Rejoindre l'organisation" }, @@ -4388,6 +4515,9 @@ "message": "Ne jamais demander de vérifier la phrase d'empreinte pour les utilisateurs invités (non recommandé)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Vous serez notifié une fois que la demande sera approuvée" + }, "free": { "message": "Gratuit", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Connectez-vous en utilisant le portail de connexion unique de votre organisation. Veuillez entrer l'identifiant de votre organisation pour commencer." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Entrez l'identifiant SSO de votre organisation pour commencer" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Pour vous connecter avec votre fournisseur de SSO, entrez l'identifiant SSO de votre organisation pour commencer. Vous devrez peut-être entrer cet identifiant SSO lorsque vous vous connecterez à partir d'un nouvel appareil." + }, "enterpriseSingleSignOn": { "message": "Portail de connexion unique d'entreprise (Single Sign-On)" }, @@ -4686,7 +4822,7 @@ "message": "Empêcher les utilisateurs de pouvoir rejoindre d'autres organisations." }, "singleOrgPolicyDesc": { - "message": "Restreindre les membres de rejoindre d'autres organisations. Cette politique est nécessaire pour les organisations qui ont activé la vérification de domaine." + "message": "Restreindre aux membres de rejoindre d'autres organisations. Cette politique de sécurité est nécessaire pour les organisations qui ont activé la vérification de domaine." }, "singleOrgBlockCreateMessage": { "message": "Votre organisation actuelle a une politique qui ne vous permet pas de rejoindre plus d'une organisation. Veuillez contacter les administrateurs de votre organisation ou vous inscrire à partir d'un compte Bitwarden différent." @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Exclu, non applicable pour cette action." }, + "nonCompliantMembersTitle": { + "message": "Membres non conformes" + }, + "nonCompliantMembersError": { + "message": "Les membres qui ne sont pas conformes à la politique d'organisation unique ou de connexion en deux étapes ne peuvent pas être restaurés jusqu'à ce qu'ils adhèrent aux exigences de la politique" + }, "fingerprint": { "message": "Empreinte" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Erreur de déchiffrement" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacter Customer Success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "pour éviter des pertes de données supplémentaires.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gérer les utilisateurs exige également d'avoir l'autorisation de gérer la restauration de compte" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Courriel envoyé" }, - "revokeSponsorshipConfirmation": { - "message": "Après avoir supprimé ce compte, le propriétaire de l'organisation Familles sera responsable de cet abonnement et des factures associées. Êtes-vous sûr de vouloir continuer ?" - }, "removeSponsorshipSuccess": { "message": "Parrainage supprimé" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Requis si l'ID d'Entité n'est pas une URL." }, + "offerNoLongerValid": { + "message": "Cette offre n'est plus valide. Contactez les administrateurs de votre organisation pour plus d'informations." + }, "openIdOptionalCustomizations": { "message": "Personnalisations Optionnelles" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Générer un courriel" }, - "generatorBoundariesHint": { - "message": "La valeur doit être comprise entre $MIN$ et $MAX$", + "spinboxBoundariesHint": { + "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Utilisez des caractères $RECOMMENDED$ ou plus pour générer un mot de passe fort.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilisez des mots $RECOMMENDED$ ou plus pour générer une phrase de passe 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Type de Nom d'Utilisateur" }, @@ -6681,6 +6857,10 @@ "message": "Fournit automatiquement aux utilisateurs et aux groupes votre fournisseur d'identité préféré via l'approvisionnement SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Fournissez automatiquement aux utilisateurs et aux groupes avec votre fournisseur d'identité préféré via l'approvisionnement SCIM. Trouvez les intégrations supportées", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Activer SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Connexion initiée" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Se souvenir de cet appareil pour rendre les connexions futures transparentes" + }, "deviceApprovalRequired": { "message": "L'approbation de l'appareil est requise. Sélectionnez une option d'approbation ci-dessous:" }, + "deviceApprovalRequiredV2": { + "message": "Autorisation de l'appareil requise" + }, + "selectAnApprovalOptionBelow": { + "message": "Sélectionnez une option d'approbation ci-dessous" + }, "rememberThisDevice": { "message": "Se souvenir de cet appareil" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approuver la demande" }, + "deviceApproved": { + "message": "Appareil approuvé" + }, + "deviceRemoved": { + "message": "Appareil supprimé" + }, + "removeDevice": { + "message": "Supprimer l'appareil" + }, + "removeDeviceConfirmation": { + "message": "Êtes-vous sûr de vouloir supprimer cet appareil ?" + }, "noDeviceRequests": { "message": "Aucune demande de l'appareil" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Courriel de l'utilisateur manquant" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Courriel de l'utilisateur actif introuvable. Vous allez être déconnecté." + }, "deviceTrusted": { "message": "Appareil de confiance" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Gérer le comportement de la collection pour l'organisation" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limiter la création et la suppression de la collection aux propriétaires et administrateurs" - }, "limitCollectionCreationDesc": { "message": "Limiter la création de collections aux propriétaires et administrateurs" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "ajouter un moyen de paiement", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informations sur l'organisation" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Utilisez Bitwarden Secrets Manager SDK dans les langages de programmation suivants pour construire vos propres applications." }, - "setUpGithubActions": { - "message": "Configurer Github Actions" + "ssoDescStart": { + "message": "Configurez", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "pour Bitwarden en utilisant le guide d'implémentation pour votre Fournisseut d'Identités.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Provisionnement de l'utilisateur" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Configurez ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Configurer Kubernetes" + "scimIntegrationDescEnd": { + "message": "(Système de gestion des identités inter-domaines) pour fournir automatiquement des utilisateurs et des groupes à Bitwarden en utilisant le guide d'implémentation de votre fournisseur d'identité.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Configurer GitLab CI/CD" + "bwdc": { + "message": "Connecteur de répertoire Bitwarden" }, - "setUpAnsible": { - "message": "Configurer Ansible" + "bwdcDesc": { + "message": "Configurez le Connecteur de Répertoire Bitwarden pour fournir automatiquement les utilisateurs et les groupes en utilisant le guide d'implémentation de votre fournisseur d'identité." }, - "rustSDKRepo": { - "message": "Afficher le dépôt Rust" + "eventManagement": { + "message": "Gestion des événements" }, - "cSharpSDKRepo": { - "message": "Afficher le dépôt C#" + "eventManagementDesc": { + "message": "Intégrez les journaux d'événements de Bitwarden à votre système SIEM (information système et gestion d'événements) en utilisant le guide d'implémentation de votre plate-forme." }, - "cPlusPlusSDKRepo": { - "message": "Afficher le dépôt C++" + "deviceManagement": { + "message": "Gestion des appareils" }, - "jsWebAssemblySDKRepo": { - "message": "Afficher le dépôt JS WebAssembly" + "deviceManagementDesc": { + "message": "Configurez la gestion des appareils pour Bitwarden en utilisant le guide d'implémentation pour votre plateforme." }, - "javaSDKRepo": { - "message": "Afficher le dépôt Java" + "integrationCardTooltip": { + "message": "Lancez le guide d'implémentation $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Mettez en place $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Afficher le dépôt Python" + "smSdkTooltip": { + "message": "Affichez le $SDK$", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Afficher le dépôt php" + "integrationCardAriaLabel": { + "message": "ouvrir le guide d'implémentation $INTEGRATION$ dans un nouvel onglet.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Afficher le dépôt Ruby" + "smSdkAriaLabel": { + "message": "affichezle dépôt $SDK$ dans un nouvel onglet.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Afficher le dépôt Go" + "smIntegrationCardAriaLabel": { + "message": "mettez en place le guide d'implémentation $INTEGRATION$ dans un nouvel onglet.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Créez une nouvelle organisation de clients à gérer en tant que Fournisseur. Des sièges supplémentaires seront reflétés lors du prochain cycle de facturation." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Service Provider géré" }, + "managedServiceProvider": { + "message": "Fournisseur de services géré" + }, + "multiOrganizationEnterprise": { + "message": "Entreprise multi-organisation" + }, "orgSeats": { "message": "Licences de l'Organisation" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Informations sur les taxes mises à jour" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Non vérifié" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB de stockage additionnel" }, + "sshKeyAlgorithm": { + "message": "Algorithme de clé" + }, + "sshKeyFingerprint": { + "message": "Empreinte digitale" + }, + "sshKeyPrivateKey": { + "message": "Clé privée" + }, + "sshKeyPublicKey": { + "message": "Clé publique" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048 bits" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072 bits" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096 bits" + }, "premiumAccounts": { "message": "6 comptes premium" }, @@ -9544,13 +9842,13 @@ "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "Pour héberger Bitwarden sur votre propre serveur, vous devrez téléverser votre fichier de licence. Pour prendre en charge les forfaits Familles Gratuits et les fonctionnalités de facturation avancées pour votre organisation auto-hébergée, vous devrez configurer la synchronisation automatique dans votre organisation auto-hébergée." + "message": "Pour héberger Bitwarden sur votre propre serveur, vous devrez téléverser votre fichier de licence. Pour prendre en charge les abonnements gratuits à Bitwarden Familles et les fonctionnalités de facturation avancées pour votre organisation auto-hébergée, vous devrez configurer la synchronisation automatique dans votre organisation auto-hébergée." }, "selfHostingTitleProper": { "message": "Auto-Hébergement" }, - "verified-domain-single-org-warning": { - "message": "La vérification d'un domaine activera la politique d'organisation unique." + "claim-domain-single-org-warning": { + "message": "Réclamer un domaine activera la politique d'organisation unique." }, "single-org-revoked-user-warning": { "message": "Les membres non conformes seront révoqués. Les administrateurs peuvent restaurer les membres une fois qu'ils quittent toutes les autres organisations." @@ -9565,12 +9863,22 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Lorsqu'un membre est supprimé, son compte Bitwarden et les données individuelles du coffre seront définitivement supprimées. Les données de Collection resteront dans l'organisation. Pour les rétablir, ils doivent créer un compte et être intégrés à nouveau.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Ceci supprimera définitivement tous les éléments appartenant à $NAME$. Les éléments de la collection ne sont pas impactés.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Ceci supprimera définitivement tous les éléments appartenant aux membres suivants. Les éléments de la collection ne sont pas impactés.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Supprimer $NAME$", + "message": "$NAME$ supprimé", "placeholders": { "name": { "content": "$1", @@ -9579,6 +9887,254 @@ } }, "organizationUserDeletedDesc": { - "message": "L'utilisateur a été supprimé de l'organisation et toutes les données utilisateur associées ont été supprimées." + "message": "L'utilisateur a été retiré de l'organisation et toutes ses données utilisateur associées ont été supprimées." + }, + "deletedUserId": { + "message": "Utilisateur supprimé $ID$ - un propriétaire / administrateur a supprimé le compte utilisateur", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "L'utilisateur $ID$ a quitté l'organisation", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ est suspendue", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contactez le propriétaire de votre organisation pour obtenir de l'aide." + }, + "suspendedOwnerOrgMessage": { + "message": "Pour regagner l'accès à votre organisation, ajoutez un mode de paiement." + }, + "deleteMembers": { + "message": "Supprimer les membres" + }, + "noSelectedMembersApplicable": { + "message": "Cette action n'est applicable à aucun des membres sélectionnés." + }, + "deletedSuccessfully": { + "message": "Supprimé avec succès" + }, + "freeFamiliesSponsorship": { + "message": "Supprimer le parrainage gratuit de Bitwarden Familles" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "N'autorisez pas les membres à échanger un plan Familles par l'intermédiaire de cette organisation." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Le paiement avec un compte bancaire est seulement disponible pour les clients aux États-Unis. Vous devrez procéder à la vérification de votre compte bancaire. Nous effectuerons un micro-dépôt dans les 2 prochains jours ouvrables. Entrez le code du descripteur de relevé de ce dépôt sur la page de facturation de l'organisation pour compléter la vérification du compte bancaire. Si vous ne complétez pas la vérification de votre compte bancaire, cela résultera en un paiement manqué et votre abonnement sera suspendu." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Nous avons effectué un micro-dépôt dans votre compte bancaire (cela peut prendre jusqu'à 2 jours ouvrables). Entrez le code à six chiffres commençant par 'SM' trouvé dans la description du dépôt. Si vous ne complétez pas la vérification de votre compte bancaire, cela résultera en un paiement manqué et votre abonnement sera suspendu." + }, + "descriptorCode": { + "message": "Code descripteur" + }, + "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": "Configurer l'identification à deux facteurs" + }, + "changeAcctEmail": { + "message": "Changer l'adresse courriel du compte" + }, + "removeMembers": { + "message": "Retirer des membres" + }, + "devices": { + "message": "Appareils" + }, + "deviceListDescription": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous. Si vous ne reconnaissez pas un appareil, supprimez-le maintenant." + }, + "deviceListDescriptionTemp": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous." + }, + "claimedDomains": { + "message": "Domaines réclamés" + }, + "claimDomain": { + "message": "Réclamer le domaine" + }, + "reclaimDomain": { + "message": "Récupérer le domaine" + }, + "claimDomainNameInputHint": { + "message": "Exemple : mondomaine.com. Les sous-domaines nécessitent des entrées séparées pour être réclamés." + }, + "automaticClaimedDomains": { + "message": "Domaines Réclamés Automatiquement" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden tentera de récupérer le domaine 3 fois pendant les 72 premières heures. Si le domaine ne peut pas être réclamé, vérifiez l'enregistrement DNS dans votre hôte et réclamez manuellement. Le domaine sera supprimé de votre organisation dans 7 jours s'il n'est pas réclamé." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ non réclamé. Vérifiez vos enregistrements DNS.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Réclamé" + }, + "domainStatusUnderVerification": { + "message": "En cours de vérification" + }, + "claimedDomainsDesc": { + "message": "Réclamez un domaine pour posséder tous les comptes membres dont l'adresse courriel correspond au domaine. Les membres pourront éviter l'identifiant SSO lors de la connexion. Les administrateurs seront également en mesure de supprimer les comptes de membre." + }, + "invalidDomainNameClaimMessage": { + "message": "L'entrée n'est pas un format valide. Format: mondomaine.com. Les sous-domaines nécessitent des entrées séparées pour être réclamés." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ réclamé", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ non réclamé", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Si vous supprimez $EMAIL$, la commandite pour ce plan Familles ne peut pas être échangée. Êtes-vous sûr de vouloir continuer ?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Si vous supprimez $EMAIL$, la commandite pour ce plan Familles prendra fin et la méthode de paiement enregistrée sera facturée $40 + taxe applicable le $DATE$. Vous ne pourrez pas réclamer un nouveau parrainage avant $DATE$. Êtes-vous sûr de vouloir continuer ?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domaine réclamé" + }, + "organizationNameMaxLength": { + "message": "Le nom de l'organisation ne doit pas dépasser 50 caractères." + }, + "resellerRenewalWarning": { + "message": "Votre abonnement sera renouvelé bientôt. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Une facture pour votre abonnement a été émise sur $ISSUED_DATE$. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "La facture de votre abonnement n'a pas été payée. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "L'abonnement à l'organisation a été redémarré" + }, + "restartSubscription": { + "message": "Redémarrez votre abonnement" + }, + "suspendedManagedOrgMessage": { + "message": "Contactez $PROVIDER$ pour obtenir de l'aide.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index b5a1650478f..3e2c1161067 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Inicios de sesión" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 930db2948e4..6db8255c46e 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "פתק מאובטח" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "התחברויות" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "גרסה $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "היסטוריית סיסמאות" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "אין סיסמאות להצגה ברשימה." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "נקה", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "אנא התחבר שוב." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "אנא התחבר שוב. אם אתה משתמש באפליקציות נוספות של Bitwarden, סגור את החיבור והתחבר שוב גם באפליקציות הללו." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "הוסרה ההרשאה מכל הסשנים" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "משתמש זה הפעיל כניסה דו שלבית כדי להגן על חשבונו." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "מכשיר" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "עדכן דפדפן" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "אתה משתמש בדפדפן אינטרנט שאיננו נתמך. כספת הרשת עלולה שלא לפעול כראוי." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "הצטרף לארגון" }, @@ -4388,6 +4515,9 @@ "message": "אל תבקש ממני לאמת את משפט טביעת האצבע יותר", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "חינם", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "הכנס באמצעות פורטל ההזדהות האחודה (SSO) הארגוני שלך. אנא הזן את המזהה הארגוני שלך כדי להתחיל." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "כניסה ארגונית אחודה" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index d815051acc1..25f7bc6e6f7 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "सुरक्षित नोट" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "लॉग इन" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index d45357dc517..610b63d68c1 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1,21 +1,24 @@ { "allApplications": { - "message": "All applications" + "message": "Sve aplikacije" }, "criticalApplications": { - "message": "Critical applications" + "message": "Kritične aplikacije" + }, + "accessIntelligence": { + "message": "Pristup inteligenciji" }, "riskInsights": { - "message": "Risk Insights" + "message": "Uvid u rizik" }, "passwordRisk": { - "message": "Password Risk" + "message": "Rizik lozinke" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Pregledaj rizične lozinke (slabe, izložene ili ponovno korištene) u svim aplikacijama. Odaberi svoje najkritičnije aplikacije za davanje prioriteta sigurnosnim radnjama da tvoji korisnici riješe rizične lozinke." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Zadnje ažuriranje: $DATE$", "placeholders": { "date": { "content": "$1", @@ -24,10 +27,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Obaviješteni članovi" + }, + "revokeMembers": { + "message": "Opozovi članove" + }, + "restoreMembers": { + "message": "Vrati članove" + }, + "cannotRestoreAccessError": { + "message": "Nije moguće vratiti pristup organizaciji" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Sve aplikacije ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -36,10 +48,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Stvori novu stavku prijave" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Kritične aplikacije ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,7 +60,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Obaviješteni članovi ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -57,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Nisu nađene aplikacije u $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -66,49 +78,49 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Kako korisnici spremaju podatke za prijavu, ovdje se pojavljuju aplikacije koje prikazuju sve rizične lozinke. Označi kritične aplikacije i obavijesti korisnike da ažuriraju lozinke." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Niti jedna aplikacija nije označena kao kritična" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "Odaberi svoje najkritičnije aplikacije za otkrivanje rizičnih lozinki i obavijesti korisnike da promijene te lozinke." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "Označi kritične aplikacije" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "Označi aplikacije kao kritične" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "Aplikacije označene kao kritične" }, "application": { - "message": "Application" + "message": "Aplikacija" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "Rizične lozinke" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Zatraži promjenu lozinke" }, "totalPasswords": { - "message": "Total passwords" + "message": "Ukupno lozinki" }, "searchApps": { - "message": "Search applications" + "message": "Pretraži aplikacije" }, "atRiskMembers": { - "message": "At-risk members" + "message": "Rizični korisnici" }, "totalMembers": { - "message": "Total members" + "message": "Ukupno korisnika" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "Rizične aplikacije" }, "totalApplications": { - "message": "Total applications" + "message": "Ukupno aplikacija" }, "whatTypeOfItem": { "message": "Koja je ovo vrsta stavke?" @@ -381,17 +393,17 @@ "message": "Boolean" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Potvrdni okvir" }, "cfTypeLinked": { "message": "Povezano", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "Vrsta polja" }, "fieldLabel": { - "message": "Field label" + "message": "Oznaka polja" }, "remove": { "message": "Ukloni" @@ -457,7 +469,7 @@ "message": "Generiraj lozinku" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generiraj fraznu lozinku" }, "checkPassword": { "message": "Provjeri je li lozinka bila ukradena." @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Sigurna bilješka" }, + "typeSshKey": { + "message": "SSH ključ" + }, "typeLoginPlural": { "message": "Prijave" }, @@ -635,7 +650,7 @@ "message": "Prikaz stavke" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Novi $TYPE$", "placeholders": { "type": { "content": "$1", @@ -644,7 +659,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Uredi $TYPE$", "placeholders": { "type": { "content": "$1", @@ -718,11 +733,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Kopiraj fraznu lozinku", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "Lozinka kopirana" }, "copyUsername": { "message": "Kopiraj korisničko ime", @@ -931,7 +946,7 @@ "message": "Razina pristupa" }, "accessing": { - "message": "Pristupanje" + "message": "Poslužitelj:" }, "loggedOut": { "message": "Odjavljen/a" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Prijava uređajem mora biti namještena u postavka Bitwarden mobilne aplikacije. Trebaš drugu opciju?" }, + "needAnotherOptionV1": { + "message": "Trebaš drugu opciju?" + }, "loginWithMasterPassword": { "message": "Prijava glavnom lozinkom" }, @@ -991,13 +1009,13 @@ "message": "Koristi drugi način prijave" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prijava pristupnim ključem" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Jedinstvena prijava (SSO)" }, "welcomeBack": { - "message": "Welcome back" + "message": "Dobro došli natrag" }, "invalidPasskeyPleaseTryAgain": { "message": "Nevažeći pristupni ključ. Pokušaj ponovno." @@ -1081,7 +1099,7 @@ "message": "Stvori račun" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novi u Bitwardenu?" }, "setAStrongPassword": { "message": "Postavi jaku lozinku" @@ -1099,11 +1117,23 @@ "message": "Prijavi se" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Prijavi se u Bitwarden" + }, + "authenticationTimeout": { + "message": "Istek vremena za autentifikaciju" + }, + "authenticationSessionTimedOut": { + "message": "Sesija za autentifikaciju je istekla. Ponovi proces prijave." }, "verifyIdentity": { "message": "Potvrdi svoj identitet" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Pokrenuta prijava" }, @@ -1233,7 +1263,7 @@ "message": "Trezor je zaključan" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Tvoj račun je zaključan" }, "uuid": { "message": "UUID" @@ -1270,7 +1300,7 @@ "message": "Nemaš prava vidjeti sve stavke u ovoj zbirci." }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "Nemaš prava za ovu kolekciju" }, "noCollectionsInList": { "message": "Nema zbirki za prikaz." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Obavijest je poslana na tvoj uređaj." }, + "aNotificationWasSentToYourDevice": { + "message": "Obavijest je poslana na tvoj uređaj" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem" + }, "versionNumber": { "message": "Verzija $VERSION_NUMBER$", "placeholders": { @@ -1306,10 +1342,10 @@ } }, "enterVerificationCodeApp": { - "message": "Unesi 6-znamenkasti kontrolni kôd iz autentifikatorske aplikacije." + "message": "Unesi 6-znamenkasti kôd za provjeru iz autentifikatorske aplikacije." }, "enterVerificationCodeEmail": { - "message": "Unesi 6-znamenkasti kontrolni kôd poslan e-poštom na $EMAIL$.", + "message": "Unesi 6-znamenkasti kôd za provjeru poslan e-poštom na $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1441,7 +1477,7 @@ "message": "Sigurno želiš nastaviti?" }, "moveSelectedItemsDesc": { - "message": "Odaberi mapu u koju želiš premjestiti odabranih $COUNT$ stavke/i.", + "message": "Odaberi mapu u koju želiš dodati odabranih $COUNT$ stavke/i.", "placeholders": { "count": { "content": "$1", @@ -1580,7 +1616,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Izbjegavaj dvosmislene znakove", "description": "Label for the avoid ambiguous characters checkbox." }, "regeneratePassword": { @@ -1605,7 +1641,7 @@ "description": "deprecated. Use numbersLabel instead." }, "specialCharacters": { - "message": "Posebni znakovi (!@#$%^&*)" + "message": "Posebni znakovi (! @ # $ % ^ & *)" }, "numWords": { "message": "Broj riječi" @@ -1621,15 +1657,33 @@ "message": "Uključi broj" }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Pravila tvrtke primjenjena su na generator.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { "message": "Povijest" }, + "generatorHistory": { + "message": "Povijest generatora" + }, + "clearGeneratorHistoryTitle": { + "message": "Očisti povijest generatora" + }, + "cleargGeneratorHistoryDescription": { + "message": "Cijela povijest generatora biti će trajno izbirsana. Sigurno želiš nastaviti?" + }, "noPasswordsInList": { "message": "Nema lozinki na popisu." }, + "clearHistory": { + "message": "Očisti povijest" + }, + "nothingToShow": { + "message": "Ništa za prikazati" + }, + "nothingGeneratedRecently": { + "message": "Ništa nije generirano" + }, "clear": { "message": "Očisti", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Molimo, ponovno se prijavi." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Molimo, ponovno se prijavi. Ako koristiš druge aplikacije Bitwarden i u njima napravi odjavu/prijavu." }, @@ -1738,7 +1798,7 @@ "message": "Pažljivo, ove akcije su konačne i ne mogu se poništiti!" }, "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" + "message": "Pažljivo, ova radnja se ne može poništiti!" }, "deauthorizeSessions": { "message": "Deautoriziraj sesije" @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Sve sesije deautorizirane" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Ovaj račun je vlasništvo $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -2201,7 +2261,7 @@ "message": "Unesi e-poštu na koju želiš primati verifikacijske kodove" }, "twoFactorEmailEnterCode": { - "message": "Unesi 6-znamenkasti verifikacijski kôd primljen e-poštom" + "message": "Unesi 6-znamenkasti kôd za provjeru primljen e-poštom" }, "sendEmail": { "message": "Pošalji poruku e-pošte" @@ -2357,7 +2417,7 @@ "message": "Provjeri izložene lozinke" }, "timesExposed": { - "message": "Times exposed" + "message": "Broj izloženosti" }, "exposedXTimes": { "message": "Izložene $COUNT$ put(a)", @@ -2394,7 +2454,7 @@ "message": "Niti jedna stavka u tvom trezoru nema slabu lozinku." }, "weakness": { - "message": "Weakness" + "message": "Slabost" }, "reusedPasswordsReport": { "message": "Izvještaj o istim lozinkama" @@ -2422,7 +2482,7 @@ "message": "Niti jedna prijava u tvom trezoru ne koristi iste lozinke." }, "timesReused": { - "message": "Times reused" + "message": "Broj ponovnih korištenja" }, "reusedXTimes": { "message": "Korišteno $COUNT$ puta", @@ -2580,7 +2640,7 @@ } }, "bitwardenFamiliesPlan": { - "message": "Plan Bitwarden Obitelji." + "message": "Paket Bitwarden Families." }, "addons": { "message": "Dodaci" @@ -2722,7 +2782,7 @@ "message": "Preuzmi licencu" }, "viewBillingToken": { - "message": "View Billing Token" + "message": "Pogledaj token za plaćanje" }, "updateLicense": { "message": "Ažuriraj licencu" @@ -2771,10 +2831,10 @@ "message": "Fakture" }, "noUnpaidInvoices": { - "message": "No unpaid invoices." + "message": "Nema neplaćenih računa." }, "noPaidInvoices": { - "message": "No paid invoices." + "message": "Nema plaćenih računa." }, "paid": { "message": "Plaćeno", @@ -3120,13 +3180,13 @@ "message": "Ljudi" }, "policies": { - "message": "Smjernice" + "message": "Pravila" }, "singleSignOn": { "message": "Jedinstvena prijava (SSO)" }, "editPolicy": { - "message": "Uredi smjernice" + "message": "Uređivanje pravila" }, "groups": { "message": "Grupe" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ovaj korisnik upotrebljava prijavu u dva koraka za zaštitu svog računa." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Pogledaj sve mogućnosti prijave" + }, "viewAllLoginOptions": { "message": "Pogledaj sve mogućnosti prijave" }, @@ -3514,7 +3580,7 @@ } }, "editedPolicyId": { - "message": "Uređene smjernice $ID$.", + "message": "Uređeno pravilo $ID$.", "placeholders": { "id": { "content": "$1", @@ -3714,6 +3780,15 @@ "device": { "message": "Uređaj" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Stvaranje računa na" }, @@ -3831,14 +3906,66 @@ "updateBrowser": { "message": "Ažuriraj preglednik" }, + "generatingRiskInsights": { + "message": "Stvaranje tvojih uvida u rizik..." + }, "updateBrowserDesc": { "message": "Koristiš nepodržani preglednik. Web trezor možda neće ispravno raditi." }, + "freeTrialEndPromptCount": { + "message": "Besplatno probno razdoblje završava za $COUNT$ dan/a.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, besplatno probno razdoblje završava za $COUNT$ dan/a.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, tvoje besplatno probno razdoblje završava sutra.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Tvoje besplatno probno razdoblje završava sutra." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, tvoje besplatno probno razdoblje završava danas.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Tvoje besplatno probno razdoblje završava danas." + }, + "clickHereToAddPaymentMethod": { + "message": "Klikni ovdje za dodavanje načina plaćanja." + }, "joinOrganization": { "message": "Pridruži se organizaciji" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Pridruži se $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -4120,7 +4247,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "Tvoja pretplata dozvoljava najviše $COUNT$ korisnika. Tvoj plan je sponzoriran i naplaćuje se vanjskoj organizaciji.", + "message": "Tvoja pretplata dozvoljava najviše $COUNT$ korisnika. Tvoj paket je sponzoriran i naplaćuje se vanjskoj organizaciji.", "placeholders": { "count": { "content": "$1", @@ -4388,6 +4515,9 @@ "message": "Nikada ne traži potvrdu jedinstvene fraze za pozvane korisnike (nije preporučeno)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Dobiti ćeš obavijest kada je tvoj zahtjev odobren" + }, "free": { "message": "Besplatno", "description": "Free, as in 'Free beer'" @@ -4436,7 +4566,7 @@ "message": "Pravila glavne lozinke" }, "masterPassPolicyDesc": { - "message": "Postavi smjernice sigurnosti koju glavna lozinka mora zadovoljiti." + "message": "Postavi pravila sigurnosti koja mora zadovoljiti glavna lozinka." }, "twoStepLoginPolicyTitle": { "message": "Zahtijevaj prijavu dvostrukom autentifikacijom" @@ -4451,13 +4581,13 @@ "message": "Član si organizacije koja zahtijeva uključenu prijavu u dva koraka na tvojem računu. Ako onemogućiš sve pružatelje prijave u dva koraka, automatski ćeš biti uklonjen/a iz organizacije." }, "passwordGeneratorPolicyDesc": { - "message": "Postavi smjernice sigurnosti koju generirana lozinka mora zadovoljiti." + "message": "Postavi pravila sigurnosti koje mora zadovoljiti generator lozinki." }, "passwordGeneratorPolicyInEffect": { - "message": "Jedna ili više organizacijskih smjernica utječe na postavke generatora." + "message": "Jedna ili više organizacijskih pravila utječe na postavke generatora." }, "masterPasswordPolicyInEffect": { - "message": "Jedna ili više organizacijskih smjernica zahtijeva da tvoja glavna lozinka ispunjava sljedeće uvjete:" + "message": "Jedna ili više organizacijskih pravila zahtijeva da tvoja glavna lozinka ispunjava sljedeće uvjete:" }, "policyInEffectMinComplexity": { "message": "Minimalna ocjena složenosti od $SCORE$", @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Prijavi se koristeći SSO portal tvoje organizacije. Za nastavak unesi identifikator organizacije." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Za početak unesi SSO identifikator" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Za prijavu sa tvojim SSO pružateljem usluga, unesi SSO identifikator svoje organizacije. Možda ćeš morati unijeti ovaj SSO identifikator kada se prijavljuješ s novog uređaja." + }, "enterpriseSingleSignOn": { "message": "Jedinstvena prijava na razini tvrtke (SSO)" }, @@ -4655,7 +4791,7 @@ "message": "SSO autentifikacija putem SAML2.0 i OpenID Connect" }, "includeEnterprisePolicies": { - "message": "Smjernice za tvrtke" + "message": "Pravila za tvrtke" }, "ssoValidationFailed": { "message": "SSO provjera nije uspjela" @@ -4686,7 +4822,7 @@ "message": "Onemogući korisnicima da se pridruže drugim organizacijama." }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "Ograniči članove da se pridruže drugim organizacijama. Ovo je pravilo potrebno za organizacije koje imaju omogućenu provjeru domene." }, "singleOrgBlockCreateMessage": { "message": "Tvoja organizacija ima pravilo koje ti ne dozvoljava pridruživanje drugim organizacijama. Molimo kontaktiraj administratora svoje organizacije ili se prijavi s privatnim Bitwarden računom." @@ -4695,7 +4831,7 @@ "message": "Članovi organizacije koji nisu Vlasnici ili Administratori, a već su članovi neke druge organizacije, biti će uklonjeni iz tvoje organizacije." }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "Nesukladni članovi bit će opozvani dok ne napuste sve druge organizacije. Administratori su izuzeti i mogu vratiti članove nakon što se ispuni usklađenost." }, "requireSso": { "message": "Zahtijevaj SSO autentifikaciju" @@ -4707,13 +4843,13 @@ "message": "Preduvjet" }, "requireSsoPolicyReq": { - "message": "Pravilo Isključive organizacije mora biti uključeno prije aktivacije ovog pravila." + "message": "Prije aktivacije ovog pravila mora se uključiti pravilo Isključive organizacije." }, "requireSsoPolicyReqError": { - "message": "Pravilo Isključive organizacije nije omogućeno." + "message": "Nije postavljeno pravilo Isključive organizacije." }, "requireSsoExemption": { - "message": "Vlasnici i Administratori organizacije nisu obuhvaćeni za provedbu ovog pravila." + "message": "Pravilo neće biti primjenjeno na Vlasnike i Administratore." }, "sendTypeFile": { "message": "Datoteka" @@ -5008,10 +5144,10 @@ "message": "Ukloni osobni trezor" }, "personalOwnershipPolicyDesc": { - "message": "Zahtijevaj korisnike spremanje stavki u trezor organizacije uklanjanjem opcije osobnog trezora." + "message": "Zahtijevaj da korisnici spremaju stavke u trezor organizacije uklanjanjem opcije osobnog trezora." }, "personalOwnershipExemption": { - "message": "Vlasnici i Administratori organizacije nisu obuhvaćeni za provedbu ovog pravila." + "message": "Vlasnici i Administratori organizacije izuzeti su od provedbe ovog pravila." }, "personalOwnershipSubmitError": { "message": "Pravila tvrtke onemogućuju spremanje stavki u osobni trezor. Promijeni vlasništvo stavke na tvrtku i odaberi dostupnu Zbirku." @@ -5020,7 +5156,7 @@ "message": "Onemogući Send" }, "disableSendPolicyDesc": { - "message": "Ne dozvoli korisnicima stvaranje ili uređivanje Sendova. Brisanje postojećeg Senda je dozvoljeno.", + "message": "Nemoj dozvoliti korisnicima stvaranje ili uređivanje Sendova. Brisanje postojećeg Senda je dozvoljeno.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { @@ -5050,7 +5186,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsPolicyInEffect": { - "message": "Organizacijske smjernice trenutno na snazi:" + "message": "Organizacijska pravila trenutno na snazi:" }, "sendDisableHideEmailInEffect": { "message": "Korisnicima nije dopušteno skrivati adresu e-pošte od primatelja kod stvaranja ili uređivanja Senda.", @@ -5415,7 +5551,7 @@ "message": "ovaj korisnik" }, "resetPasswordMasterPasswordPolicyInEffect": { - "message": "Jedna ili više organizacijskih smjernica zahtijeva da glavna lozinka ispunjava sljedeće uvjete:" + "message": "Jedno ili više organizacijskih pravila zahtijeva da glavna lozinka ispunjava sljedeće uvjete:" }, "resetPasswordSuccess": { "message": "Uspješno ponovno postalvjena lozinka!" @@ -5433,7 +5569,7 @@ "message": "Postojeći računi s glavnim lozinkama zahtijevat će od članova da se sami učlane prije nego što im administratori mogu oporaviti račune. Automatsko učlanjenje uključit će oporavak računa i za nove članove." }, "accountRecoverySingleOrgRequirementDesc": { - "message": "Pravilo Isključive organizacije mora biti uključeno prije aktivacije ovog pravila." + "message": "Prije aktivacije ovog pravila mora se uključiti pravilo Isključive organizacije." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatsko učlanjenje" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Isključeno, nije primjenjivo na ovu radnju" }, + "nonCompliantMembersTitle": { + "message": "Nesukladni članovi" + }, + "nonCompliantMembersError": { + "message": "Članovi koji nisu u skladu s Isključivom organizacijom ili politikom prijave s dvostrukom autentifikacijom ne mogu se vratiti dok god se ne pridržavaju pravila" + }, "fingerprint": { "message": "Otisak prsta" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Upravljanje korisnicima mora također biti uključeno s dozvolom za Upravljanje ponovnim postavljanjem lozinke" }, @@ -5781,7 +5937,7 @@ "message": "Onemogući izvoz osobnog trezora" }, "disablePersonalVaultExportDescription": { - "message": "Onemogućuje korisnike da izvezu svoj osobni trezor." + "message": "Onemogućuje korisnicima izvoz osobnog trezora." }, "vaultExportDisabled": { "message": "Izvoz trezora onemogućen" @@ -5922,16 +6078,16 @@ "message": "Konfiguracija za jedinstvenu prijavu (SSO) je spremljena." }, "sponsoredFamilies": { - "message": "Besplatan obiteljski Bitwarden" + "message": "Besplatan Bitwarden Families" }, "sponsoredFamiliesEligible": { - "message": "Ti i tvoja obitelj ispunjavate uvjete za besplatni obiteljski Bitwarden. Iskoristiti ponudu svojom e-poštom kako bi zaštitio svoje podatke čak i kada nisi na poslu." + "message": "Ti i tvoja obitelj ispunjavate uvjete za besplatni Bitwarden Families. Iskoristiti ponudu svojom e-poštom kako bi zaštitio svoje podatke čak i kada nisi na poslu." }, "sponsoredFamiliesEligibleCard": { - "message": "Iskoristi svoju ponudu za besplatni obiteljski Bitwarden već danas kako bi tvoji podaci bili sigurni čak i kada nisi na poslu." + "message": "Iskoristi svoju ponudu za besplatni Bitwarden Families paket već danas kako bi tvoji podaci bili sigurni čak i kada nisi na poslu." }, "sponsoredFamiliesInclude": { - "message": "Bitwarden obiteljski plan uključuje" + "message": "Bitwarden Families paket uključuje" }, "sponsoredFamiliesPremiumAccess": { "message": "Premium pristup do 6 korisnika" @@ -5952,19 +6108,19 @@ "message": "Odaberi organizaciju koju želiš sponzorirati" }, "familiesSponsoringOrgSelect": { - "message": "Koju ponudu besplatnog obiteljskog plana želiš iskoristiti?" + "message": "Koju ponudu besplatnog Families paketa želiš iskoristiti?" }, "sponsoredFamiliesEmail": { - "message": "Unesi svoju osobnu e-poštu za korištenje obiteljskog Bitwardena" + "message": "Unesi svoju privatnu e-poštu za korištenje Bitwarden Families" }, "sponsoredFamiliesLeaveCopy": { - "message": "Ako ukloniš ponudu ili budeš uklonjen/a iz sponzorske organizacije, tvoje će sponzorstvo Obiteljskog plana isteći na sljedeći datum obnove." + "message": "Ako ukloniš ponudu ili budeš uklonjen/a iz sponzorske organizacije, tvoje će sponzorstvo Families paketa isteći na sljedeći datum obnove." }, "acceptBitwardenFamiliesHelp": { - "message": "Prihavati ponudu postojeće organizacije ili stvori novu obiteljsku organizaciju." + "message": "Prihavati ponudu postojeće organizacije ili stvori novu Families organizaciju." }, "setupSponsoredFamiliesLoginDesc": { - "message": "Ponuđen ti je besplatni obiteljski Bitwarden plan. Za nastavak, prijavi se korisničkim računom koji je dobio ponudu." + "message": "Ponuđen ti je besplatni Bitwarden Families paket. Za nastavak, prijavi se korisničkim računom koji je dobio ponudu." }, "sponsoredFamiliesAcceptFailed": { "message": "Nije moguće prihvatiti ponudu. Ponovo pošalji e-poštu ponude sa svog poslovnog računa i pokušaj ponovno." @@ -5979,10 +6135,10 @@ } }, "sponsoredFamiliesOffer": { - "message": "Prihvati besplatan obiteljski Bitwarden" + "message": "Prihvati besplatan Bitwarden Families" }, "sponsoredFamiliesOfferRedeemed": { - "message": "Besplatna ponuda obiteljski Bitwarden je uspješno iskorištena" + "message": "Besplatna ponuda Bitwarden Families je uspješno iskorištena" }, "redeemed": { "message": "Kôd iskorišten" @@ -6009,7 +6165,7 @@ } }, "freeFamiliesPlan": { - "message": "Besplatan obiteljski plan" + "message": "Besplatni Families paket" }, "redeemNow": { "message": "Iskoristi kôd sada" @@ -6029,9 +6185,6 @@ "emailSent": { "message": "e-pošta poslana" }, - "revokeSponsorshipConfirmation": { - "message": "Nakon uklanjanja ovog računa, sponzorstvo Obiteljskog plana isteći će na kraju obračunskog razdoblja. Nećeš moći iskoristiti novu ponudu sponzorstva dok ne istekne postojeća. Sigurno želiš nastaviti?" - }, "removeSponsorshipSuccess": { "message": "Sponzorstvo uklonjeno" }, @@ -6115,7 +6268,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { - "message": "zahtijevaj SSO autentifikaciju i pravila isključive organizacije", + "message": "zahtijevaj SSO autentifikaciju i pravila jedne organizacije", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { @@ -6150,7 +6303,7 @@ "message": "Sponzorirana ponuda je istekla. Kako na kraju 7 dnevnog probnog razdoblja ne bi došlo do terećenja, moguće je izbrisati stvorenu organizaciju. Zadržavanjem organizacije preuzimate obvezu plaćanja." }, "newFamiliesOrganization": { - "message": "Nova obiteljska organizacija" + "message": "Nova Families organizacija" }, "acceptOffer": { "message": "Prihvati ponudu" @@ -6177,7 +6330,7 @@ "message": "Pogledaj token za sinkronizaciju naplate" }, "generateBillingToken": { - "message": "Generate billing token" + "message": "Generiraj token za plaćanje" }, "copyPasteBillingSync": { "message": "Kopiraj i zalijepi ovaj token u postavke sinkronizacije naplate tvoje organizacije s vlastitim poslužiteljem." @@ -6186,7 +6339,7 @@ "message": "Tvoj token za sinkronizaciju naplate može pristupiti i uređivati postavke pretplate ove organizacije." }, "manageBillingTokenSync": { - "message": "Manage Billing Token" + "message": "Upravljaj tokenom za plaćanje" }, "setUpBillingSync": { "message": "Podesi sinkronizaciju naplate" @@ -6240,7 +6393,7 @@ "message": "Vlastiti poslužitelj" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "Za postavljanje svoje organizacije na vlastitom poslužitelju, morat ćeš prenijeti datoteku licence. Kako bi podržali planove Free Families i napredne mogućnosti naplate za svoju samostalnu organizaciju, morat ćeš postaviti sinkronizaciju naplate." + "message": "Za postavljanje svoje organizacije na vlastitom poslužitelju, morat ćeš prenijeti datoteku licence. Kako bi podržali besplatne Families pakete i napredne mogućnosti naplate za svoju samostalnu organizaciju, morat ćeš postaviti sinkronizaciju naplate." }, "billingSyncApiKeyRotated": { "message": "Token rotiran" @@ -6252,7 +6405,7 @@ "message": "Token za sinkronizaciju naplate" }, "automaticBillingSyncDesc": { - "message": "Automatic sync unlocks Families sponsorships and allows you to sync your license without uploading a file. After making updates in the Bitwarden cloud server, select Sync License to apply changes." + "message": "Automatska sinkronizacija otključava Families sponzorstva i omogućuje sinkronizaciju licence bez učitavanja datoteke. Nakon ažuriranja Bitwarden poslužitelja u oblaku, odaberi Sinkronizacija Licence za primjenu promjena." }, "active": { "message": "Aktivno" @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Obavezan Enditiy ID nije URL." }, + "offerNoLongerValid": { + "message": "Ova ponuda više ne vrijedi. Obrati se administratorima svoje organizacije za više informacija." + }, "openIdOptionalCustomizations": { "message": "Izborne prilagodbe" }, @@ -6411,10 +6567,10 @@ "message": "Generiraj korisničko ime" }, "generateEmail": { - "message": "Generate email" + "message": "Generiraj e-poštu" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Vrijednost mora biti u rasponu $MIN$ - $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Koristi $RECOMMENDED$ i više znakova za generiranje jake lozinke.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Koristi $RECOMMENDED$ i više riječi za generiranje jake frazne lozinke.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tip korisničkog imena" }, @@ -6533,11 +6709,11 @@ "message": "Generiraj pseudonim e-pošte s vanjskom uslugom prosljeđivanja." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domena e-pošte", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Odaberi domenu koju podržava odabrani servis", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -6662,7 +6838,7 @@ "message": "Provjera uređaja ažurirana" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Sigurno želiš uključiti provjeru uređaja? e-pošta s kontrolnim kôdom stići će na: $EMAIL$", + "message": "Sigurno želiš uključiti provjeru uređaja? e-pošta s kôdom za provjeru stići će na: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -6681,6 +6857,10 @@ "message": "Automatski korisnicima i grupama dodijeli željenog pružatelja identiteta putem SCIM dodjeljivanja", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatski korisnicima i grupama dodijeli željenog pružatelja identiteta putem SCIM dodjeljivanja. Pronađi podržane integracije", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Uključi SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -6800,10 +6980,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 polje treba tvoju pažnju." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ polja treba(ju) tvoju pažnju.", "placeholders": { "count": { "content": "$1", @@ -7660,7 +7840,7 @@ "message": "Prenesi datoteku" }, "upload": { - "message": "Upload" + "message": "Prijenos" }, "acceptedFormats": { "message": "Prihvaćeni formati:" @@ -7672,13 +7852,13 @@ "message": "ili" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Otključaj biometrijom" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Otključaj PIN-om" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Otključaj glavnom lozinkom" }, "licenseAndBillingManagement": { "message": "Upravljanje licencama i naplatom" @@ -7690,7 +7870,7 @@ "message": "Ručni prijenos" }, "manualBillingTokenUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships." + "message": "Ako ne želiš uključiti sinkronizaciju naplate, ovdje ručno prenesi svoju licencu. Time se neće automatski otključati Families sponzorstva." }, "syncLicense": { "message": "Sinkroniziraj licencu" @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Prijava pokrenuta" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamti ovaj uređaj kako bi buduće prijave bile brže" + }, "deviceApprovalRequired": { "message": "Potrebno je odobriti uređaj. Odaberi metodu odobravanja:" }, + "deviceApprovalRequiredV2": { + "message": "Potrebno odobrenje uređaja" + }, + "selectAnApprovalOptionBelow": { + "message": "Odaberi opciju odobrenja" + }, "rememberThisDevice": { "message": "Zapamti ovaj uređaj" }, @@ -7983,27 +8172,27 @@ "message": "Pouzdani uređaji" }, "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Jednom autentificirani, korisnici će moći dešifrirati podatke u trezoru koristeći svoju glavnu lozinku", + "message": "Jednom autentificirani, korisnici će moći dešifrirati podatke u trezoru koristeći ključ spremljen na njihovom uređaju. ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "isključivo-organizacijska", + "message": "pravilo Isključive organizacije", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "politika,", + "message": ",", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "obavezna SSO", + "message": "pravilo obavezne SSO autentifikacije", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "politika i ", + "message": "pravilo i ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "politika administracije povrata računa", + "message": "pravilo administracije povrata računa", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartFour": { @@ -8019,7 +8208,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "od $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Odobri zahtjev" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nema zahtjeva na čekanju" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Nedostaje e-pošta korisnika" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Nije pronađena e-pošta aktivnog korisnika. Odjava u tijeku..." + }, "deviceTrusted": { "message": "Uređaj pouzdan" }, @@ -8285,14 +8489,11 @@ "collectionManagementDesc": { "message": "Upravljaj ponašanjem zbirki za organizaciju" }, - "limitCollectionCreationDeletionDesc": { - "message": "Ograniči kreiranje i brisanje zbirki vlasnicima i adminima" - }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "Omogući kreiranje zbirki samo vlasnicima i adminima" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "Omogući brisanje zbirki samo vlasnicima i adminima" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Vlasnici i admini mogu upravljati svim zbirkama i stavkama" @@ -8340,7 +8541,7 @@ "message": "URL poslužitelja" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL vlastitog poslužitelja", "description": "Label for field requesting a self-hosted integration service URL" }, "aliasDomain": { @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "dodaj način plaćanja", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Info o organizaciji" @@ -8552,7 +8753,7 @@ "message": "Dodaj polje" }, "editField": { - "message": "Edit field" + "message": "Uredi polje" }, "items": { "message": "Stavke" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Koristi Bitwarden Secrets Manager SDK u sljedećim programskim jezicima za izradu vlastitih aplikacija." }, - "setUpGithubActions": { - "message": "Postavi Github Actions" + "ssoDescStart": { + "message": "Podesi", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "za Bitwarden koristeći vodič za implementaciju za tvojeg davatelja identiteta.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Postavi Kubernetes" + "userProvisioning": { + "message": "Korisničko dodijeljivanje" }, - "setUpGitlabCICD": { - "message": "Postavi GitLab CI/CD" + "scimIntegration": { + "message": "SCIM" }, - "setUpAnsible": { - "message": "Postavi Ansible" + "scimIntegrationDescStart": { + "message": "Podesi ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "rustSDKRepo": { - "message": "Postavi Rust repozitorij" + "scimIntegrationDescEnd": { + "message": "(Sustav za upravljanje identitetom između domena) za automatsko dodjeljivanje korisnika i grupa Bitwardenu korištenjem vodiča za implementaciju za vašeg pružatelja identiteta.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "Pogledaj C# repozitorij" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "cPlusPlusSDKRepo": { - "message": "Pogledaj C++ repozitorij" + "bwdcDesc": { + "message": "Konfiguriraj Bitwarden Directory Connector za automatsko dodjeljivanje korisnika i grupa pomoću vodiča za implementaciju za vašeg pružatelja identiteta." }, - "jsWebAssemblySDKRepo": { - "message": "Pogledaj JS WebAssembly repozitorij" + "eventManagement": { + "message": "Upravljanje događajima" }, - "javaSDKRepo": { - "message": "Pogledaj Java repozitorij" + "eventManagementDesc": { + "message": "Integrirajte Bitwarden zapisnike događaja sa svojim SIEM (system information and event management) sustavom pomoću vodiča za implementaciju za vašu platformu." }, - "pythonSDKRepo": { - "message": "Pogledaj Python repozitorij" + "deviceManagement": { + "message": "Upravljanje uređajima" }, - "phpSDKRepo": { - "message": "Pogledaj php repozitorij" + "deviceManagementDesc": { + "message": "Konfiguriraj upravljanje uređajima za Bitwarden pomoću vodiča za implementaciju za svoju platformu." }, - "rubySDKRepo": { - "message": "Pogledaj Ruby repozitorij" + "integrationCardTooltip": { + "message": "Pokreni vodič za implementaciju $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "goSDKRepo": { - "message": "Pogledaj Go repozitorij" + "smIntegrationTooltip": { + "message": "Postavi $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "Pogledaj $SDK$ repozitorij", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "otvori vodič za implementaciju $INTEGRATION$ u novoj kartici.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "pogledaj $SDK$ repozitorij u novoj kartici.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "postavi vodič za implementaciju $INTEGRATION$ u novoj kartici.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Stvori novu klijentsku organizaciju kojom ćeš upravljati kao Pružatelj. Dodatna mjesta bit će vidljiva u sljedećem ciklusu naplate." @@ -8945,19 +9201,19 @@ "message": "Upravljanje naplatom s Portala pružatelja usluga" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "Nastavi s postavljanjem besplatne probe Bitwardena" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "Nastavi s postavljanjem besplatne probe Bitwarden Upravitelja Lozinki" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "Nastavi s postavljanjem besplatne probe Btwarden Secrets Managera" }, "enterTeamsOrgInfo": { "message": "Unesi podatke za organizaciju Timovi" }, "enterFamiliesOrgInfo": { - "message": "Unesi podatke za organizaciju Obitelji" + "message": "Unesi podatke za Families organizaciju" }, "enterEnterpriseOrgInfo": { "message": "Unesi podatke za organizaciju Tvrtke" @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Upravljani pružatelj usluga" }, + "managedServiceProvider": { + "message": "Upravljani pružatelj usluga" + }, + "multiOrganizationEnterprise": { + "message": "Tvrtka s više organizacija" + }, "orgSeats": { "message": "Organizacijska mjesta" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Ažurirane porezne informacije" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Nepotvrđeno" }, @@ -9264,64 +9538,64 @@ "message": "kupljenih mjesta uklonjeno" }, "environmentVariables": { - "message": "Environment variables" + "message": "Varijable okruženja" }, "organizationId": { - "message": "Organization ID" + "message": "ID organizacije" }, "projectIds": { - "message": "Project IDs" + "message": "Projekt ID" }, "projectId": { - "message": "Project ID" + "message": "Projekt ID" }, "projectsAccessedByMachineAccount": { - "message": "The following projects can be accessed by this machine account." + "message": "Sljedećim projektima može se pristupiti putem ovog strojnog računa." }, "config": { - "message": "Config" + "message": "Konfiguracija" }, "learnMoreAboutEmergencyAccess": { - "message": "Learn more about emergency access" + "message": "Saznaj više o pristupu u nuždi" }, "learnMoreAboutMatchDetection": { - "message": "Learn more about match detection" + "message": "Saznaj više o otkrivanju podudaranja" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Learn more about master password re-prompt" + "message": "Saznaj više o ponovnom upitu za glavnu lozinku" }, "learnMoreAboutSearchingYourVault": { - "message": "Learn more about searching your vault" + "message": "Saznaj više o pretraživanju svojeg trezora" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Learn about your account fingerprint phrase" + "message": "Saznaj više o jedinstvenoj frazi tvog računa" }, "impactOfRotatingYourEncryptionKey": { - "message": "Impact of rotating your encryption key" + "message": "Utjecaj rotiranja tvojeg ključa za šifriranje" }, "learnMoreAboutEncryptionAlgorithms": { - "message": "Learn more about encryption algorithms" + "message": "Saznaj više o algoritmima šifriranja" }, "learnMoreAboutKDFIterations": { - "message": "Learn more about KDF iterations" + "message": "Saznaj više o KDF iteracijama" }, "learnMoreAboutLocalization": { - "message": "Learn more about localization" + "message": "Saznaj više o prevođenju" }, "learnMoreAboutWebsiteIcons": { - "message": "Learn more about using website icons" + "message": "Saznaj više o korištenju ikona web stranica" }, "learnMoreAboutUserAccess": { - "message": "Learn more about user access" + "message": "Saznaj više o pristupu korisnika" }, "learnMoreAboutMemberRoles": { - "message": "Learn more about member roles and permissions" + "message": "Saznaj više o članskim ulogama i pravima" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "Što je CVV broj?" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "Saznaj više o Bitwarden API" }, "fileSends": { "message": "Send datoteke" @@ -9363,7 +9637,7 @@ "message": "SSO autentifikacija" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "Organizacije Obitelji mogu imati ovoliko članova: $SEATCOUNT$. Nadogradi na plaćeni plan za povećanje broja članova.", + "message": "Families organizacije mogu imati ovoliko članova: $SEATCOUNT$. Nadogradi na plaćeni plan za povećanje broja članova.", "placeholders": { "seatcount": { "content": "$1", @@ -9372,7 +9646,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "Organizacije Obitelji mogu imati ovoliko članova: $SEATCOUNT$. Kontaktiraj vlasnika organizacije za nadogradnju.", + "message": "Families organizacije mogu imati ovoliko članova: $SEATCOUNT$. Kontaktiraj vlasnika organizacije za nadogradnju.", "placeholders": { "seatcount": { "content": "$1", @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB dodatnog prostora" }, + "sshKeyAlgorithm": { + "message": "Algoritam ključa" + }, + "sshKeyFingerprint": { + "message": "Otisak prsta" + }, + "sshKeyPrivateKey": { + "message": "Privatni ključ" + }, + "sshKeyPublicKey": { + "message": "Javni ključ" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium računa" }, @@ -9459,7 +9757,7 @@ "message": "Trenutno" }, "secretsManagerSubscriptionInfo": { - "message": "Your Secrets Manager subscription will upgrade based on the plan selected" + "message": "Tvoja pretplata na Secrets Manager će se nadograditi na temelju odabranog plana" }, "bitwardenPasswordManager": { "message": "Bitwarden upravitelj lozinki" @@ -9484,22 +9782,22 @@ "message": "Uredi pristup" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Koristi tekstualna polja za podatke poput sigurnosnih pitanja" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Koristi skrivena polja za osjetljive podatke poput lozinke" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Koristi potvrdne okvire ako ih želiš auto-ispuniti u obrascu, npr. zapamti adresu e-pošte" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "Koristi povezano polje kada imaš problema s auto-ispunom za određenu web stranicu." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "Unesi html id polja, naziv, aria-label ili rezervirano mjesto." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Uključi velika slova", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9507,7 +9805,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Uključi mala slova", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9515,7 +9813,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Uključi brojeve", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9523,40 +9821,40 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Uključi posebne znakove", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { - "message": "!@#$%^&*", + "message": "! @ # $ % ^ & *", "description": "Label for the password generator special characters checkbox" }, "addAttachment": { - "message": "Add attachment" + "message": "Dodaj privitak" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Najveća veličina datoteke je 500 MB" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Sigurno želiš trajno izbrisati ovaj privitak?" }, "manageSubscriptionFromThe": { - "message": "Manage subscription from the", + "message": "Upravljaj pretplatom iz datoteke", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." + "message": "Za smještanje Bitwardena na vlastiti poslužitelj, morat ćeš prenijeti datoteku licence. Kako bi podržali besplatne Families pakete i napredne mogućnosti naplate za svoj poslužitelj, morat ćeš postaviti automatsku sinkronizaciju." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "Vlastiti poslužitelj" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Polaganje prava na domenu uključit će pravilo Isključive organizacije." }, "single-org-revoked-user-warning": { - "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." + "message": "Nesukladni članovi bit će opozvani. Administratori mogu vratiti članove nakon što napuste sve druge organizacije." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Izbriši $NAME$", "placeholders": { "name": { "content": "$1", @@ -9565,12 +9863,22 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Ovo će trajno obrisati sve stavke čiji vlasnik je $NAME$. Ne odnosi se na zbirke.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Ovo će trajno obrisati sve stavke sljedećih vlasnika. Ne odnosi se na zbirke.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ izbrisano", "placeholders": { "name": { "content": "$1", @@ -9579,6 +9887,254 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "Korisnik je uklonjen iz organizacije i svi povezani korisnički podaci su izbrisani." + }, + "deletedUserId": { + "message": "Izbrisan korisnik $ID$ - vlasnik/admin je izbrisao korisnički račun", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Korisnik $ID$ je napustio organizaciju", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ je suspendirana", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Za pomoć se obrati vlasniku organizacije." + }, + "suspendedOwnerOrgMessage": { + "message": "Za ponovni pristup svojoj organizaciji, dodaj način plaćanja." + }, + "deleteMembers": { + "message": "Obriši članove" + }, + "noSelectedMembersApplicable": { + "message": "Ova radnja nije primjenjiva niti na jednog odabranog korisnika." + }, + "deletedSuccessfully": { + "message": "Uspješno izbrisano" + }, + "freeFamiliesSponsorship": { + "message": "Ukloni besplatno sponzorstvo Bitwarden Families" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Nemoj dopustiti članovima da koriste Families paket putem ove organizacije." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Plaćanje putem bankovnog računa dostupno je samo kupcima u SAD. Trebat ćeš potvrditi svoj bankovni račun. Unutar 1 - 2 radna dana izvršit ćemo mikro-uplatu. Unesi šifru u opisu ove uplate na stranici za naplatu organizacije za potvrdu bankovni račun. Nepotvrda bankovnog računa rezultirat će propuštenim plaćanjem i obustavom pretplate." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Izvršili smo mikro-uplatu na bankovni račun (ovo može potrajati 1 - 2 radna dana). Unesi šesteroznamenkasti kôd koji počinje sa 'SM' i nalazi se u opisu uplate. Nepotvrda bankovnog računa rezultirat će propuštenim plaćanjem i obustavom pretplate." + }, + "descriptorCode": { + "message": "Šifra uplate" + }, + "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" + }, + "removeMembers": { + "message": "Ukloni članove" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Potvrđene domene" + }, + "claimDomain": { + "message": "Potvrdi domenu" + }, + "reclaimDomain": { + "message": "Ponovno potvrdi domenu" + }, + "claimDomainNameInputHint": { + "message": "Primjer: mydomain.com. Poddomene je potrebno zasebno potvrditi." + }, + "automaticClaimedDomains": { + "message": "Automatski potvrđene domene" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden će pokušati potvrditi domenu 3 puta tijekom prva 72 sata. Ako se domena ne može potvrditi, provjeri DNS zapis na svom poslužitelju i ručno potvrdi. Domena će, ako se ne potvrdi, biti uklonjena iz vaše organizacije nakon 7 dana." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ nije potvrđena. Provjeri DNS zapise.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Potvrđena" + }, + "domainStatusUnderVerification": { + "message": "Provjera u tijeku" + }, + "claimedDomainsDesc": { + "message": "Potvrdi domenu za vlasništvo nad svim računima članova čija adresa e-pošte odgovara domeni. Članovi će moći preskočiti SSO identifikator prilikom prijave. Administratori će također moći brisati članske račune." + }, + "invalidDomainNameClaimMessage": { + "message": "Unos nije važeći. Format: mojadomena.hr Poddomene zahtijevaju zasebne unose za provjeru." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ potvrđena", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ nije potvrđena", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Ako ukloniš $EMAIL$, sponzorstvo za ovaj Families paket se neće moći iskoristiti. Sigurno želiš nastaviti?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Ako ukloniš $EMAIL$, sponzorstvo za ovaj Families paket če završiti, a na spremljeni način plaćanja će ovaj dartuM: $DATE$ biti naplaćeno 40 USD + primjenjivi porez. Nećeš moći iskoristiti novo sponzorstvo do $DATE$. Sigurno želiš nastaviti?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domena potvrđena" + }, + "organizationNameMaxLength": { + "message": "Naziv organizacije ne može biti duži od 50 znakova." + }, + "resellerRenewalWarning": { + "message": "Tvoja će se pretplata uskoro obnoviti. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Za tvoju je pretplatu $ISSUED_DATE$ izdana faktura. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Faktura za tvoju pretplatu nije plaćena. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnovu prije $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 95ecaecc6b4..163355b20d9 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kritikus alkalmazások" }, + "accessIntelligence": { + "message": "Elérés intelligencia" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Kockázati betekintés" }, "passwordRisk": { "message": "Jelszó kockázat" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Tekintsük meg a veszélyeztetett jelszavakat (gyenge, nyilvános vagy újrafelhasznált) az alkalmazásokban. Válasszuk ki a legkritikusabb alkalmazásokat, hogy előnyben részesítsük a biztonsági műveleteket a felhasználók számára a veszélyeztetett jelszavak kezeléséhez." }, "dataLastUpdated": { "message": "Az adatok utolsó frissítése: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Értesített tagok" }, + "revokeMembers": { + "message": "Tagok eltávolítása" + }, + "restoreMembers": { + "message": "Tagok visszaállítása" + }, + "cannotRestoreAccessError": { + "message": "Nem lehet visszaállítani a szervezeti hozzáférést." + }, "allApplicationsWithCount": { "message": "Összes alkalmazás ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Biztonságos jegyzet" }, + "typeSshKey": { + "message": "SSH kulcs" + }, "typeLoginPlural": { "message": "Bejelentkezések" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Az eszközzel történő bejelentkezést be kell állítani a Bitwarden alkalmazás beállításaiban. Más opcióra van szükség?" }, + "needAnotherOptionV1": { + "message": "Másik opció szükséges?" + }, "loginWithMasterPassword": { "message": "Bejelentkezés mesterjelszóval" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Bejelentkezés a Bitwardenbe" }, + "authenticationTimeout": { + "message": "Hitelesítési időkifutás" + }, + "authenticationSessionTimedOut": { + "message": "A hitelesítési munkamenet időkifutással lejárt. Indítsuk újra a bejelentkezési folyamatot." + }, "verifyIdentity": { "message": "Személyazonosság ellenőrzése" }, + "whatIsADevice": { + "message": "Mi az eszköz?" + }, + "aDeviceIs": { + "message": "Az eszköz a Bitwarden alkalmazás egyedi telepítése, amelyre bejelentkeztünk. Az alkalmazás adatok újratelepítése, törlése vagy a sütik törlése azt eredményezheti, hogy egy eszköz többször is megjelenhet." + }, "logInInitiated": { "message": "A bejelentkezés elindításra került." }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A rendszer értesítést küldött az eszközre." }, + "aNotificationWasSentToYourDevice": { + "message": "Egy értesítés lett elküldve az eszközre." + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Ellenőrizzük, hogy a széf feloldásra került és az ujjlenyomat kifejezés egyezik a másik eszközön levővel." + }, "versionNumber": { "message": "Verzió: $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Jelszóelőzmények" }, + "generatorHistory": { + "message": "Generátor előzmények" + }, + "clearGeneratorHistoryTitle": { + "message": "Generátor előzmények kiürítése" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ha folytatjuk, az összes bejegyzés véglegesen törlődik a generátor előzményeiből. Biztosan folytatjuk?" + }, "noPasswordsInList": { "message": "Nincsenek listázható jelszavak." }, + "clearHistory": { + "message": "Előzmények törlése" + }, + "nothingToShow": { + "message": "Nincs megjeleníthető elem" + }, + "nothingGeneratedRecently": { + "message": "Mostanában nem lett semmi generálva." + }, "clear": { "message": "Kiürítés", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Ismételten be kell jelentkezni." }, + "currentSession": { + "message": "Jelenlegi munkamenet" + }, + "requestPending": { + "message": "Függőben lévő kérelem" + }, "logBackInOthersToo": { "message": "Ismételten be kell jelentkezni. Ha másik Bitwarden alkalmazásokat használunk, ott is jelentkezzünk ki és ismételten be." }, @@ -1752,7 +1812,7 @@ "sessionsDeauthorized": { "message": "Az összes munkamenet hitelesítése eldobásra került." }, - "accountIsManagedMessage": { + "accountIsOwnedMessage": { "message": "Ezt a fiókot $ORGANIZATIONNAME$ kezeli.", "placeholders": { "organizationName": { @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 meghívó maradt." + }, "userUsingTwoStep": { "message": "Ez a felhasználó kétlépcsős bejelentkezést használ fiókja védelmére." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Összes bejelentkezési opció megtekintése" + }, "viewAllLoginOptions": { "message": "Összes bejelentkezési opció megtekintése" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Eszköz" }, + "loginStatus": { + "message": "Bejelentkezési állapot" + }, + "firstLogin": { + "message": "Első bejelentkezés" + }, + "trusted": { + "message": "Megbízható" + }, "creatingAccountOn": { "message": "Fiók létrehozása:" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Böngésző frissítése" }, + "generatingRiskInsights": { + "message": "A kockázati betekintések generálása..." + }, "updateBrowserDesc": { "message": "Nem támogatott böngészőt használunk. Előfordulhat, hogy a webes széf nem működik megfelelően." }, + "freeTrialEndPromptCount": { + "message": "Az ingyenes próbaidőszak $COUNT$ nap múlva ér véget.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, az ingyenes próbaidőszak $COUNT$ nap múlva lejár.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, az ingyenes próbaidőszak holnap lejár.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Az ingyenes próbaidőszak holnap lejár." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, az ingyenes próbaidőszak ma lejár.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Az ingyenes próbaidőszak ma lejár." + }, + "clickHereToAddPaymentMethod": { + "message": "Kattintás ide egy fizetési mód hozzáadásához." + }, "joinOrganization": { "message": "Csatlakozás szervezethez" }, @@ -4388,6 +4515,9 @@ "message": "Soha ne kérjük a meghívottak ujjlenyomat kifejezésének ellenőrzését (nem ajánlott)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "A kérelem jóváhagyása után értesítés érkezik." + }, "free": { "message": "Ingyenes", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Bejelentkezés a szervezeti önálló portálba. A kezdéshez meg kell adni a szervezeti azonosítót." }, + "singleSignOnEnterOrgIdentifier": { + "message": "A kezdéshez adjuk meg a szervezet egyszeri bejelentkezési azonosítóját." + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Az SSO szolgáltatóval bejelentkezéshez adjuk meg a szervezet SSO azonosítót. Előfordulhat, hogy meg kell adni ezt az SSO azonosítót új eszközről bejelentkezéskor." + }, "enterpriseSingleSignOn": { "message": "Vállalati önálló bejelentkezés" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Kizárva, nem alkalmazható erre a műveletre." }, + "nonCompliantMembersTitle": { + "message": "Nem megfelelő tagok" + }, + "nonCompliantMembersError": { + "message": "Azok a tagok, amelyek nem felelnek meg az egy szervezeti vagy kétlépcsős bejelentkezési szabályzatnak, nem állíthatók vissza mindaddig, amíg nem felelnek meg a szabályzat követelményeinek." + }, "fingerprint": { "message": "Ujjlenyomat" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A felhasználók kezelését engedélyezni kell a Jelszó visszaállításának kezelése jogosultsággal is." }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Az email elküldésre került." }, - "revokeSponsorshipConfirmation": { - "message": "A fiók eltávolítása után a Családok szervezetének tulajdonosa lesz felelős az előfizetésért és a kapcsolódó számlákért. Biztosan folytatjuk?" - }, "removeSponsorshipSuccess": { "message": "A szponzoráció eltávolításra került." }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Szükséges ha az entitás azonosító nem webcím." }, + "offerNoLongerValid": { + "message": "Ez az ajánlat már nem érvényes. További információkért vegyük fel a kapcsolatot a szervezeti adminisztrátorokkal." + }, "openIdOptionalCustomizations": { "message": "Opcionális testreszabások" }, @@ -6413,7 +6569,7 @@ "generateEmail": { "message": "Email generálása" }, - "generatorBoundariesHint": { + "spinboxBoundariesHint": { "message": "Az érték legyen $MIN$ és $MAX$ között.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Használjunk $RECOMMENDED$ vagy több karaktert egy erős jelszó előállításához.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": "Használjunk $RECOMMENDED$ vagy több szót erős jelmondat generálásához.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Felhasználónév típusa" }, @@ -6681,6 +6857,10 @@ "message": "A felhasználók és csoportok automatikusan biztosítása a kívánt identitás szolgáltatóval a SCIM szolgáltatáson keresztül.", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "A felhasználók és csoportok automatikusan biztosítása a kívánt identitás szolgáltatóval a SCIM szolgáltatáson keresztül. Támogatott integrációk keresése", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM engedélyezése", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "A bejelentkezés elindításra került." }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Emlékezés az eszközre, hogy zökkenőmentes legyen a jövőbeni bejelentkezés" + }, "deviceApprovalRequired": { "message": "Az eszköz jóváhagyása szükséges. Válasszunk egy jóváhagyási lehetőséget lentebb:" }, + "deviceApprovalRequiredV2": { + "message": "Eszköz jóváhagyás szükséges" + }, + "selectAnApprovalOptionBelow": { + "message": "Válasszunk lentebb egy jóváhagyási lehetőséget." + }, "rememberThisDevice": { "message": "Eszköz megjegyzése" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Kérés megerősítése" }, + "deviceApproved": { + "message": "Az eszköz jóváhagyásra került." + }, + "deviceRemoved": { + "message": "Az eszköz eltávolításra került." + }, + "removeDevice": { + "message": "Eszköz eltávolítása" + }, + "removeDeviceConfirmation": { + "message": "Biztos eltávolításra kerüljön ez az eszköz?" + }, "noDeviceRequests": { "message": "Nincs eszköz kérés" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "A felhasználói email cím hiányzik." }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "" + }, "deviceTrusted": { "message": "Az eszköz megbízható." }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Gyűjtési viselkedést kezelhet a szervezetnek" }, - "limitCollectionCreationDeletionDesc": { - "message": "A gyűjtemény létrehozásának és törlésének korlátozása tulajdonosokra és adminisztrátorokra" - }, "limitCollectionCreationDesc": { "message": "A gyűjtemény létrehozásának korlátozása tulajdonosokra és adminisztrátorokra" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "fitetési metódus hozzáadása", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Szervezeti információ" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Konfigurálás", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Kubernetes beüzemelése" + "ssoDescEnd": { + "message": "a Bitwarden számára az azonosítás szolgáltató megvalósítási útmutató használatával.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "Felhasználói kiépítés" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "Rust tár megtekintése" + "scimIntegrationDescStart": { + "message": "Konfigurálás ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(Rendszer a tartományok közötti azonosítás kezeléshez), hogy automatikusan biztosítson felhasználókat és csoportokat a Bitwarden számára az azonostíás szolgáltató megvalósítási útmutatója segítségével.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Konfigurálja a Bitwarden Directory Connector szolgáltatást úgy, hogy automatikusan biztosítson felhasználókat és csoportokat az identitásszolgáltató megvalósítási útmutatója segítségével." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Esemény kezelés" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrálja a Bitwarden eseménynaplókat a SIEM (rendszerinformációs és esemény kezelési) rendszerrel a platform megvalósítási útmutatója segítségével." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Eszközkezelés" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Az eszközkezelés konfigurálása a Bitwarden számára a platform megvalósítási útmutatója segítségével." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "$INTEGRATION$ megvalósítási útmutató elindítása.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "$INTEGRATION$ beüzemelése.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "$SDK$ tár megtekintése", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "$INTEGRATION$ megvalósítási útmutató megnyitása egy új fülön.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "$SDK$ tár megtekintése egy új fülön.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "$INTEGRATION$ megvalósítási útmutató beüzemelése egy új fülön.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Menedzselt szolgáltató" }, + "managedServiceProvider": { + "message": "Menedzselt szolgáltató" + }, + "multiOrganizationEnterprise": { + "message": "Több-szervezetű vállalkozás" + }, "orgSeats": { "message": "Szervezeti helyek" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Frissített adó információ" }, + "billingInvalidTaxIdError": { + "message": "Érvénytelen az adóazonosító. Ha úgy gondoljuk, hogy ez hiba, forduljunk az ügyfélszolgálathoz." + }, + "billingTaxIdTypeInferenceError": { + "message": "Nem lehetett ellenőrizni az adóazonosítót. Ha úgy gondoljuk, hogy ez hiba, forduljunk az ügyfélszolgálathoz." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Érvénytelen az adóazonosító. Ha úgy gondoljuk, hogy ez hiba, forduljunk az ügyfélszolgálathoz." + }, + "billingPreviewInvoiceError": { + "message": "Hiba történt a számla előnézete közben. Próbáljuk újra később." + }, "unverified": { "message": "Nem ellenőrzött" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB kiegészítő tároló" }, + "sshKeyAlgorithm": { + "message": "Kulcs algoritmus" + }, + "sshKeyFingerprint": { + "message": "Ujjlenyomat" + }, + "sshKeyPrivateKey": { + "message": "Személyes kulcs" + }, + "sshKeyPublicKey": { + "message": "Nyilvános kulcs" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 prémium fiók" }, @@ -9547,9 +9845,9 @@ "message": "Ha a Bitwarden szolgáltatást saját szerveren szeretnénk tárolni, fel kell tölteni a licenszfájlt. A Free Families csomagok és a speciális számlázási lehetőségek támogatásához a saját üzemeltetésű szervezet számára be kell állítani az automatikus szinkronizálást a saját üzemeltetésű szervezetben." }, "selfHostingTitleProper": { - "message": "Sahát üzemeltetés" + "message": "Saját üzemeltetés" }, - "verified-domain-single-org-warning": { + "claim-domain-single-org-warning": { "message": "A tartomány ellenőrzése bekapcsolja az önálló szervezet házirendjét." }, "single-org-revoked-user-warning": { @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Amikor egy tag törlésre kerül, a Bitwarden fiókjuk és az egyéni széf adataik véglegesen törlésre kerülnek. A gyűjtési adatok a szervezetben maradnak. A visszaállításukhoz egy fiókot kell létrehozni és újra be kell lépni.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Ezzel véglegesen törlésre kerül $NAME$ tulajdonában lévő összes elem. A gyűjtemény elemeit ez nem érinti.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Ezzel véglegesen törlésre kerül a következő tagok tulajdonában lévő összes elem. A gyűjtemény elemeit ez nem érinti.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "$NAME$ törlésre került.", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "A felhasználó eltávolításra került a szervezetből és az összes kapcsolódó felhasználói adat törlésre került." + }, + "deletedUserId": { + "message": "$ID$ felhasználó törlésre került – egy tulajdonos/adminisztrátor törölte a felhasználói fiókot.", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "$ID$ felhasználó elhagyta a szervezetet.", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ felfüggesztésre került.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Segítségért forduljunk a szervezet tulajdonosához." + }, + "suspendedOwnerOrgMessage": { + "message": "A szervezethez hozzáférés visszaszerzéséhez adjunk hozzá egy fizetési módot." + }, + "deleteMembers": { + "message": "Tagok törlése" + }, + "noSelectedMembersApplicable": { + "message": "Ez a művelet egyik kiválasztott tagra sem alkalmazható." + }, + "deletedSuccessfully": { + "message": "A törlés sikeres volt." + }, + "freeFamiliesSponsorship": { + "message": "Az ingyenes Bitwarden Családok szponzorálásának eltávolítása" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Ne engedjük meg a tagoknak, hogy beváltsák a Családi csomagot ezen a szervezeten keresztül." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "A bankszámlával történő fizetés csak az Egyesült Államokban élő ügyfelek számára érhető el. A bankszámlát igazolni kell. A fejlesztők a következő 1-2 munkanapon belül mikrobetétet teljesítenek. A bankszámla ellenőrzéséhez írjuk be az utalás leíró kódját a szervezet számlázási oldalán. Ha elmulasztjuk a bankszámla igazolását, az a fizetés elmaradását és az előfizetés felfüggesztését vonja maga után." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "A fejlesztők mikrobetétet utaltak bankszámlánkra (ez 1-2 munkanapot is igénybe vehet). Adjuk meg a befizetés leírásában található, 'SM' kezdetű hatjegyű kódot. Ha elmulasztjuk a bankszámla igazolását, az a fizetés elmaradását és az előfizetés felfüggesztését vonja maga után." + }, + "descriptorCode": { + "message": "Leíró kód" + }, + "importantNotice": { + "message": "Fontos megjegyzés" + }, + "setupTwoStepLogin": { + "message": "Kétlépéses bejelentkezés szükséges" + }, + "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" + }, + "removeMembers": { + "message": "Tagok eltávolítása" + }, + "devices": { + "message": "Eszközök" + }, + "deviceListDescription": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett. Ha nem ismerünk fel egy eszközt, távolítsuk el most." + }, + "deviceListDescriptionTemp": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett." + }, + "claimedDomains": { + "message": "Igényelt tartományok" + }, + "claimDomain": { + "message": "Tartomány igénylés" + }, + "reclaimDomain": { + "message": "Tartomány visszaszerzés" + }, + "claimDomainNameInputHint": { + "message": "Példa: valami.hu. Az altartományokhoz külön bejegyzések szükségesek az ellenőrzéshez." + }, + "automaticClaimedDomains": { + "message": "Automatikusan igényelt tartományok" + }, + "automaticDomainClaimProcess": { + "message": "A Bitwarden az első 72 óra során 3 alkalommal kísérli meg a tartomány ellenőrzését. Ha a tartomány nem ellenőrizhető, ellenőrizésre kerül a DNS rekordt a kiszolgálón és az ellenőrzés manuálisan történik. A tartomány 7 napon belül eltávolításra kerül, ha nem kerül igénylésre." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ nincs igényelve. Ellenőrizzük a DNS rekordot.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Igényelve" + }, + "domainStatusUnderVerification": { + "message": "Ellenőrzés alatt" + }, + "claimedDomainsDesc": { + "message": "Igényeljünk egy tartományt az összes olyan tagfiók birtoklásához, amelynek email címe megegyezik a tartománnyal A tagok bejelentkezéskor kihagyhatják az egyszeri bejelentkezési azonosítót. Az adminisztrátorok törölhetik a tagfiókokat is." + }, + "invalidDomainNameClaimMessage": { + "message": "A bemeneti formátum nem érvényes. Formátum: valami.hu Az altartományokhoz külön bejegyzések szükségesek az igényléshez." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ igénylésre került.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ nem került igénylésre.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Ha eltávolítjuk $EMAIL$ email címet, a családi csomag szponzorálása nem váltható be. Biztosan folytatjuk?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Ha eltávolítjuk $EMAIL$ fiókot, a családi csomag szponzorálása megszűnik és a mentett fizetési módot több, mint 40 amerikai dollár vonatkozó adóval lesz megterhelve: $DATE$. $DATE$ időpontig nem válthatunk be új szponzorációt. Biztosan folytatjuk?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "A tartomány követelésre került." + }, + "organizationNameMaxLength": { + "message": "A szervezet neve nem haladhatja meg az 50 karaktert." + }, + "resellerRenewalWarning": { + "message": "Az előfizetés hamarosan megújul. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $RENEWAL_DATE$ előtt.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Az előfizetésről szóló számla kiállítása $ISSUED_DATE$ napon történt. A megszakítás nélküli szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $DUE_DATE$ előtt.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Az előfizetésről szóló számla nem lett kfizetve. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $GRACE_PERIOD_END$ előtt.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "A szervezeti feliratkozás újraindult." + }, + "restartSubscription": { + "message": "Feliratkozás újra indítása" + }, + "suspendedManagedOrgMessage": { + "message": "Segítséget $PROVIDER$ szolgáltatótól kaphatunk.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 0a9b35b4b12..3b861fb16da 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Catatan Aman" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Masuk" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Masuk dengan kata sandi utama" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versi $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Riwayat Kata Sandi" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Tidak ada sandi yang dapat dicantumkan." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Bersihkan", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Harap masuk kembali." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Harap masuk kembali. Jika Anda menggunakan aplikasi Bitwarden lain, keluarlah dan masuk kembali ke sana juga." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Semua Sesi Dicabut Izinnya" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Pengguna ini menggunakan proses masuk dua langkah untuk melindungi akun mereka." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Lihat semua opsi log in" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Perangkat" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Perbarui Browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Anda menggunakan browser web yang tidak didukung. Kubah web mungkin tidak berfungsi dengan baik." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Bergabunglah dengan Organisasi" }, @@ -4388,6 +4515,9 @@ "message": "Jangan tanya untuk memverifikasi frase sidik jari lagi", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Masuk menggunakan portal sistem masuk tunggal organisasi Anda. Harap masukkan pengenal organisasi Anda untuk memulai." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Sistem Masuk Tunggal Perusahaan" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Sidik Jari" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email Terkirim" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship dibuang" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Diperlukan jika Entity ID bukan sebuah URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Kustomisasi Opsional" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Jenis nama pengguna" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 5f7a48bf3a8..8ce01a4737d 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -5,17 +5,20 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { - "message": "Rischio password" + "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { - "message": "Ultimo aggiornamento: $DATE$", + "message": "Data last updated: $DATE$", "placeholders": { "date": { "content": "$1", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Membri notificati" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "Tutte le applicazioni ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Nota sicura" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Login" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "L'accesso con dispositivo deve essere abilitato nelle impostazioni dell'app Bitwarden. Ti serve un'altra opzione?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Accedi con password principale" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verifica la tua identità" }, + "whatIsADevice": { + "message": "Cos'è un dispositivo?" + }, + "aDeviceIs": { + "message": "Un dispositivo è un'installazione unica dell'app Bitwarden con la quale hai effettuato l'accesso. Reinstallazione, eliminazione dei dati dell'app o cancellazione dei cookie potrebbero causare la comparsa di un dispositivo più volte." + }, "logInInitiated": { "message": "Login avviato" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versione $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Cronologia delle password" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Non ci sono password da mostrare." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Cancella", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Accedi di nuovo." }, + "currentSession": { + "message": "Sessione attuale" + }, + "requestPending": { + "message": "Richiesta in attesa" + }, "logBackInOthersToo": { "message": "Accedi di nuovo. Se stai usando Bitwarden su altre app, esci e rientra anche in quelle." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Tutte le sessioni revocate" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Hai ancora 1 invito." + }, "userUsingTwoStep": { "message": "Questo utente usa la verifica in due passaggi per proteggere il suo account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Visualizza tutte le opzioni di accesso" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Stato accesso" + }, + "firstLogin": { + "message": "Primo accesso" + }, + "trusted": { + "message": "Di fiducia" + }, "creatingAccountOn": { "message": "Creazione account su" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Aggiorna browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Stai utilizzando un browser non supportato. La cassaforte web potrebbe non funzionare correttamente." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Unisciti all'organizzazione" }, @@ -4388,6 +4515,9 @@ "message": "Non chiedermi di verificare la frase impronta per utenti invitati (non consigliato)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Accedi usando il portale di accesso (SSO) della tua organizzazione. Inserisci l'identificativo della tua organizzazione per iniziare." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Single Sign-On aziendale" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Escluso, non applicabile per questa azione" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Impronta" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gestisci utenti deve essere abilitato con il permesso di gestire il ripristino delle password" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email inviata" }, - "revokeSponsorshipConfirmation": { - "message": "Dopo aver rimosso questo account, il piano di sponsorizzazione Families scadrà alla fine del periodo di fatturazione. Non potrai riscuotere una nuova offerta di sponsorizzazione finché quella esistente non scade. Sei sicuro di voler continuare?" - }, "removeSponsorshipSuccess": { "message": "Sponsorizzazione rimossa" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Obbligatorio se l'ID dell'entità non è un URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Personalizzazioni facoltative" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo di nome utente" }, @@ -6681,6 +6857,10 @@ "message": "Approvvigiona utenti e gruppi automaticamente con il tuo fornitore di identità preferito tramite l'approvvigionamento SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Abilita SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Accesso avviato" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Approvazione del dispositivo obbligatoria. Seleziona un'opzione di approvazione:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Ricorda questo dispositivo" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approva richiesta" }, + "deviceApproved": { + "message": "Dispositivo approvato" + }, + "deviceRemoved": { + "message": "Dispositivo rimosso" + }, + "removeDevice": { + "message": "Rimuovi dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Sei sicuro di voler rimuovere il dispositivo?" + }, "noDeviceRequests": { "message": "Nessuna richiesta da approvare" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Email utente mancante" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Dispositivo fidato" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Gestisci il comportamento delle raccolte per l'organizzazione" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limita la creazione e l'eliminazione delle raccolte a proprietari e amministratori" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "aggiungi un metodo di pagamento", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informazioni sull'organizzazione" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Usa l'SDK di Bitwarden Secrets Manager nei seguenti linguaggi di programmazione per creare le tue applicazioni." }, - "setUpGithubActions": { - "message": "Configura GitHub Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Configura Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Configura GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Configura Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "Visualizza il repository Rust" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "Visualizza il repository C#" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "Visualizza il repository C++" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "Visualizza il repository JS WebAssembly" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "Visualizza il repository Java" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "Visualizza il repository Python" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "Visualizza il repository PHP" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "Visualizza il repository Ruby" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "Visualizza il repository Go" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Crea una nuova organizzazione cliente da gestire come fornitore. Gli slot aggiuntivi saranno riflessi nel prossimo ciclo di fatturazione." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Fornitore di servizi gestiti" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Slot dell'organizzazione" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Informazioni fiscali aggiornate" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Non verificato" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB di spazio aggiuntivo" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 account premium" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Dispositivi" + }, + "deviceListDescription": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto. Se non riconosci un dispositivo, rimuovilo ora." + }, + "deviceListDescriptionTemp": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Abbonamento organizzazione riavviato" + }, + "restartSubscription": { + "message": "Riavvia il tuo abbonamento" + }, + "suspendedManagedOrgMessage": { + "message": "Contatta $PROVIDER$ per assistenza.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 979c22eb99d..44213d143aa 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -3,19 +3,22 @@ "message": "すべてのアプリ" }, "criticalApplications": { - "message": "Critical applications" + "message": "きわめて重要なアプリ" + }, + "accessIntelligence": { + "message": "アクセス インテリジェンス" }, "riskInsights": { - "message": "Risk Insights" + "message": "リスク分析" }, "passwordRisk": { - "message": "Password Risk" + "message": "パスワードのリスク" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "危険なパスワード(強度が低い、流出済み、再利用)を、アプリをまたいで調査します。特に重要なアプリを選択して、危険なパスワードに優先対応するようユーザーに促しましょう。" }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "データの最終更新: $DATE$", "placeholders": { "date": { "content": "$1", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "通知済みメンバー" }, + "revokeMembers": { + "message": "メンバーを削除" + }, + "restoreMembers": { + "message": "メンバーを復元" + }, + "cannotRestoreAccessError": { + "message": "組織へのアクセスを復元できません" + }, "allApplicationsWithCount": { "message": "すべてのアプリ ($COUNT$)", "placeholders": { @@ -36,10 +48,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "新しいログインアイテムを作成" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "特に重要なアプリ ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -57,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "$ORG NAME$ にアプリが見つかりませんでした", "placeholders": { "org name": { "content": "$1", @@ -66,22 +78,22 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "ユーザーがログイン情報を保存すると、アプリがここに表示され、リスクの高いパスワードがあれば表示されます。特に重要なアプリはマークして、パスワードを更新するようにユーザーに通知しましょう。" }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "重要なアプリとしてマークしたものがありません" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "特に重要なアプリケーションを選択して、危険なパスワードを発見し、ユーザーにパスワードを変更するよう通知しましょう。" }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "重要なアプリをマークする" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "重要なアプリとしてマーク" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "重要なアプリとしてマークされました" }, "application": { "message": "アプリ" @@ -90,13 +102,13 @@ "message": "リスクがあるパスワード" }, "requestPasswordChange": { - "message": "Request password change" + "message": "パスワードの変更を要求する" }, "totalPasswords": { "message": "合計パスワード数" }, "searchApps": { - "message": "Search applications" + "message": "アプリを検索" }, "atRiskMembers": { "message": "リスクがあるメンバー" @@ -457,7 +469,7 @@ "message": "パスワードの自動生成" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "パスフレーズを生成" }, "checkPassword": { "message": "パスワードが漏洩していないか確認する" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "セキュアメモ" }, + "typeSshKey": { + "message": "SSH 鍵" + }, "typeLoginPlural": { "message": "ログイン" }, @@ -718,11 +733,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "パスフレーズをコピー", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "パスワードをコピーしました" }, "copyUsername": { "message": "ユーザー名のコピー", @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Bitwarden アプリで「デバイスでログイン」の設定をする必要があります。別のオプションが必要ですか?" }, + "needAnotherOptionV1": { + "message": "別の選択肢が必要ですか?" + }, "loginWithMasterPassword": { "message": "マスターパスワードでログイン" }, @@ -991,13 +1009,13 @@ "message": "別のログイン方法を使用する" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "パスキーでログイン" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "シングルサインオンを使用する" }, "welcomeBack": { - "message": "Welcome back" + "message": "ようこそ" }, "invalidPasskeyPleaseTryAgain": { "message": "無効なパスキーです。もう一度やり直してください。" @@ -1081,7 +1099,7 @@ "message": "アカウントの作成" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden は初めてですか?" }, "setAStrongPassword": { "message": "強力なパスワードを設定する" @@ -1099,11 +1117,23 @@ "message": "ログイン" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden にログイン" + }, + "authenticationTimeout": { + "message": "認証のタイムアウト" + }, + "authenticationSessionTimedOut": { + "message": "認証セッションの有効期限が切れました。ログイン操作を最初からやり直してください。" }, "verifyIdentity": { "message": "本人確認" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ログイン開始" }, @@ -1270,7 +1300,7 @@ "message": "このコレクション内のアイテムをすべて表示する権限がありません。" }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "このコレクションの利用権限がありません" }, "noCollectionsInList": { "message": "表示するコレクションがありません" @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "デバイスに通知を送信しました。" }, + "aNotificationWasSentToYourDevice": { + "message": "お使いのデバイスに通知が送信されました" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末と一致していることを確認してください" + }, "versionNumber": { "message": "バージョン $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "パスワードの履歴" }, + "generatorHistory": { + "message": "生成履歴" + }, + "clearGeneratorHistoryTitle": { + "message": "生成履歴を消去" + }, + "cleargGeneratorHistoryDescription": { + "message": "続行すると、すべてのエントリは生成履歴から完全に削除されます。続行してもよろしいですか?" + }, "noPasswordsInList": { "message": "表示するパスワードがありません" }, + "clearHistory": { + "message": "履歴を消去" + }, + "nothingToShow": { + "message": "表示するものがありません" + }, + "nothingGeneratedRecently": { + "message": "最近生成したものはありません" + }, "clear": { "message": "消去する", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "ログインし直してください" }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ログインし直してください。他のBitwardenのアプリを使用している場合、同様にログインし直してください。" }, @@ -1738,7 +1798,7 @@ "message": "これらの操作はやり直せないため注意してください!" }, "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" + "message": "この操作は元に戻せないため注意してください!" }, "deauthorizeSessions": { "message": "セッションの承認を取り消す" @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "全てのセッションを無効化" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "このアカウントは $ORGANIZATIONNAME$ が所有しています", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "このユーザーはアカウントを保護するため二段階認証を利用しています。" }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "すべてのログインオプションを表示" + }, "viewAllLoginOptions": { "message": "すべてのログインオプションを表示" }, @@ -3714,6 +3780,15 @@ "device": { "message": "デバイス" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "アカウント作成:" }, @@ -3831,14 +3906,66 @@ "updateBrowser": { "message": "ブラウザを更新" }, + "generatingRiskInsights": { + "message": "リスク分析を生成しています..." + }, "updateBrowserDesc": { "message": "サポートされていないブラウザを使用しています。ウェブ保管庫が正しく動作しないかもしれません。" }, + "freeTrialEndPromptCount": { + "message": "無料体験はあと $COUNT$ 日で終了します。", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$ 様、無料体験はあと $COUNT$ 日で終了します。", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$ 様、無料体験は明日で終了します。", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "無料体験は明日終了します。" + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$ 様、無料体験は本日終了します。", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "無料体験は本日で終了します。" + }, + "clickHereToAddPaymentMethod": { + "message": "支払い方法を追加するにはここをクリックしてください。" + }, "joinOrganization": { "message": "組織に参加" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "$ORGANIZATIONNAME$ に参加", "placeholders": { "organizationName": { "content": "$1", @@ -4388,6 +4515,9 @@ "message": "今後パスフレーズを確認しない", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "リクエストが承認されると通知されます" + }, "free": { "message": "無料", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "組織のシングルサインオンポータルを使用してログインします。開始するには組織の識別子を入力してください。" }, + "singleSignOnEnterOrgIdentifier": { + "message": "組織の SSO ID を入力して開始します" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "SSO プロバイダーでログインするには、組織の SSO ID を入力して開始します。新しいデバイスからログインする際に、この SSO ID を入力する必要がある場合があります。" + }, "enterpriseSingleSignOn": { "message": "組織のシングルサインオン" }, @@ -4686,7 +4822,7 @@ "message": "ユーザーが他の組織に参加できないように制限します。" }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "メンバーに対し、他の組織への参加を制限します。ドメイン認証を有効にしている組織では、このポリシーは必須となります。" }, "singleOrgBlockCreateMessage": { "message": "現在の組織には、複数の組織に参加することを許可していないポリシーがあります。 組織の管理者に連絡するか、別の Bitwarden アカウントから登録してください。" @@ -4695,7 +4831,7 @@ "message": "オーナーまたは管理者でなく、すでに他の組織のメンバーであるメンバーは組織から削除されます。" }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "ポリシーに準拠していないメンバーは、他のすべての組織から退出するまで失効状態になります。これは管理者には適用されず、管理者はコンプライアンスが満たされたメンバーを復元できます。" }, "requireSso": { "message": "シングルサインオン認証" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "除外します。このアクションには適用されません。" }, + "nonCompliantMembersTitle": { + "message": "非準拠のメンバー" + }, + "nonCompliantMembersError": { + "message": "単一組織ポリシーまたは2段階ログインポリシーに準拠していないメンバーは、ポリシー要件を遵守するまで復元できません。" + }, "fingerprint": { "message": "指紋" }, @@ -5537,6 +5679,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "ユーザーを管理するには、アカウントのリカバリ管理権限を付与する必要があります。" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "メールが送信されました" }, - "revokeSponsorshipConfirmation": { - "message": "このアカウントを削除した後、家族向けプランのスポンサーシップは請求期間の終了時に失効します。 既存のスポンサーオファーの有効期限が切れるまで、新しいスポンサーオファーを引き換えることはできません。続行してもよろしいですか?" - }, "removeSponsorshipSuccess": { "message": "スポンサーシップを削除しました" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "エンティティ ID が URL でない場合は必須です。" }, + "offerNoLongerValid": { + "message": "このオファーは無効になりました。詳しくは組織の管理者にお問い合わせください。" + }, "openIdOptionalCustomizations": { "message": "オプションのカスタマイズ" }, @@ -6411,10 +6567,10 @@ "message": "ユーザー名を生成" }, "generateEmail": { - "message": "Generate email" + "message": "メールアドレスを生成" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "ユーザー名の種類" }, @@ -6533,11 +6709,11 @@ "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": { @@ -6681,6 +6857,10 @@ "message": "SCIM プロビジョニングにより、ユーザーとグループを希望する ID プロバイダーで自動的にプロビジョニングします。", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "SCIMプロビジョニングにより、お好みのID プロバイダでユーザーとグループを自動的にプロビジョニングします。利用可能な連携サービスを探す。", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM を有効にする", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "ログイン開始" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "このデバイスを記憶して今後のログインをシームレスにする" + }, "deviceApprovalRequired": { "message": "デバイスの承認が必要です。以下から承認オプションを選択してください:" }, + "deviceApprovalRequiredV2": { + "message": "デバイスの承認が必要です" + }, + "selectAnApprovalOptionBelow": { + "message": "以下の承認オプションを選択してください" + }, "rememberThisDevice": { "message": "このデバイスを記憶する" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "リクエストを承認" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "デバイスリクエストはありません" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "ユーザーのメールアドレスがありません" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "アクティブなユーザーメールアドレスが見つかりません。ログアウトします。" + }, "deviceTrusted": { "message": "信頼されたデバイス" }, @@ -8285,14 +8489,11 @@ "collectionManagementDesc": { "message": "組織のコレクションに関する挙動を管理します" }, - "limitCollectionCreationDeletionDesc": { - "message": "コレクションの作成・削除を所有者と管理者のみに制限" - }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "コレクションの作成を所有者と管理者のみに制限" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "コレクションの削除を所有者と管理者のみに制限" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "所有者と管理者はすべてのコレクションとアイテムを管理できます" @@ -8340,7 +8541,7 @@ "message": "サーバー URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自己ホスト型サーバー URL", "description": "Label for field requesting a self-hosted integration service URL" }, "aliasDomain": { @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "支払い方法を追加してください", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "組織情報" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Bitwarden シークレットマネージャー SDK を以下のプログラミング言語で使用して、独自のアプリを構築できます。" }, - "setUpGithubActions": { - "message": "Github アクションを設定" + "ssoDescStart": { + "message": "設定", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "id プロバイダの実装ガイドを参照してください。", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "ユーザプロビジョニング" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Kubernetes を設定" + "scimIntegrationDescStart": { + "message": "設定 ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "GitLab CI/CD の設定" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) を使用して、identity Providerの実装ガイドに従って、ユーザーとグループを自動的にBitwardenにプロビジョニングします。", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Ansible を設定" + "bwdc": { + "message": "Bitwarden ディレクトリコネクタ" }, - "rustSDKRepo": { - "message": "Rust リポジトリを表示" + "bwdcDesc": { + "message": "Bitwarden Directory Connector を設定し、Identity Provider(Idプロバイダー)の実装ガイドを使用して、ユーザーとグループを自動的にプロビジョニングするようにしてください。" }, - "cSharpSDKRepo": { - "message": "C# リポジトリを表示" + "eventManagement": { + "message": "イベント管理" }, - "cPlusPlusSDKRepo": { - "message": "C++ リポジトリを表示" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "JS WebAssembly リポジトリを表示" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "Java リポジトリを表示" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Python リポジトリを表示" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "PHP リポジトリを表示" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Ruby リポジトリを表示" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Go リポジトリを表示" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "プロバイダーとして管理するための新しいクライアント組織を作成します。次の請求サイクルに追加のシートが反映されます。" @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "マネージドサービスプロバイダー" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "組織のシート" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "更新された税情報" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "未認証" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB の追加ストレージ" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6個のプレミアムアカウント" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "これにより、 $NAME$が所有するすべてのアイテムが完全に削除されます。コレクションアイテムに影響はありません。", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "これにより、以下のメンバーが所有するすべてのアイテムが永久に削除されます。コレクション アイテムは影響を受けません。", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "メンバーを削除" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 3f837d2745a..610bc0f6d87 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "უსაფრთხო ჩანაწერი" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "მომხმარებლები" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "ავტორიზაცია მთავარი პაროლით" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ავტორიზაცია დაწყებულია" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "შეტყობინება გამოიგზავნა თქვენი ტელეფონის მისამართით." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "ვერსია $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 57cc9a404e1..e6cce3405d5 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 867ad7c2624..215bde966ad 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "ಸುರಕ್ಷಿತ ಟಿಪ್ಪಣಿ" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Master password ಉಪಯೋಗಿಸಿ ಲಾಗಿನ್ ಮಾಡಿ" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "ಆವೃತ್ತಿ $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "ಪಾಸ್ವರ್ಡ್ ಇತಿಹಾಸ" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "ಪಟ್ಟಿ ಮಾಡಲು ಯಾವುದೇ ಪಾಸ್ವರ್ಡ್ಗಳು ಇಲ್ಲ." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "ಕ್ಲಿಯರ್‌", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ. ನೀವು ಇತರ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬಳಸುತ್ತಿದ್ದರೆ ಲಾಗ್ಔಟ್ ಮಾಡಿ ಮತ್ತು ಅವುಗಳಿಗೆ ಹಿಂತಿರುಗಿ." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "ಎಲ್ಲಾ ಸೆಷನ್‌ಗಳು ಅನಧಿಕೃತವಾಗಿವೆ" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "ಈ ಬಳಕೆದಾರರು ತಮ್ಮ ಖಾತೆಯನ್ನು ರಕ್ಷಿಸಲು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಬಳಸುತ್ತಿದ್ದಾರೆ." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "ಡಿವೈಸ್" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "ಬ್ರೌಸರ್ ನವೀಕರಿಸಿ" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "ನೀವು ಬೆಂಬಲಿಸದ ವೆಬ್ ಬ್ರೌಸರ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ. ವೆಬ್ ವಾಲ್ಟ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೆ ಇರಬಹುದು." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "ಸಂಸ್ಥೆಗೆ ಸೇರಿ" }, @@ -4388,6 +4515,9 @@ "message": "ಫಿಂಗರ್ಪ್ರಿಂಟ್ ನುಡಿಗಟ್ಟು ಮತ್ತೆ ಪರಿಶೀಲಿಸಲು ಕೇಳಬೇಡಿ", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "ಉಚಿತ", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "ನಿಮ್ಮ ಸಂಸ್ಥೆಯ ಏಕ ಸೈನ್-ಆನ್ ಪೋರ್ಟಲ್ ಬಳಸಿ ಲಾಗ್ ಇನ್ ಮಾಡಿ. ಪ್ರಾರಂಭಿಸಲು ದಯವಿಟ್ಟು ನಿಮ್ಮ ಸಂಸ್ಥೆಯ ಗುರುತಿಸುವಿಕೆಯನ್ನು ನಮೂದಿಸಿ." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "ಎಂಟರ್‌ಪ್ರೈಸ್ ಏಕ ಸೈನ್-ಆನ್" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "ಹೊರಗಿಡಲಾಗಿದೆ, ಈ ಕ್ರಿಯೆಗೆ ಅನ್ವಯಿಸುವುದಿಲ್ಲ." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "ಫಿಂಗರ್‌ಪ್ರಿಂಟ್" }, @@ -5537,6 +5679,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 8b7474784a5..0cd1302ea2f 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "보안 메모" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "로그인" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "기기로 로그인은 Bitwarden 앱 설정에서 구성돼야 합니다. 다른 방식이 필요하신가요?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "마스터 비밀번호로 로그인" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "기기에 알림이 전송되었습니다." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "버전 $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "비밀번호 변경 기록" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "비밀번호가 없습니다." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "삭제", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "다시 로그인해 주세요." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "다시 로그인해 주세요. 다른 Bitwarden 앱을 사용 중인 경우 해당 앱에서도 다시 로그인해야 합니다." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "모든 세션 해제 됨" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "이 사용자는 계정을 보호하기 위해 2단계 로그인을 사용하고 있습니다." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "모든 로그인 방식 보기" }, @@ -3714,6 +3780,15 @@ "device": { "message": "기기" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "브라우저 업데이트" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "지원하지 않는 웹 브라우저를 사용하고 있습니다. 웹 보관함 기능이 제대로 동작하지 않을 수 있습니다." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "조직 참가" }, @@ -4388,6 +4515,9 @@ "message": "초대된 사용자의 지문 구절을 확인하지 않음 (권장되지 않음)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "무료", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "조직의 통합 인증(SSO) 포탈을 통해서 로그인하세요. 시작하려면 조직 식별자를 입력해주세요." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "엔터프라이즈 통합 인증 (SSO)" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "제외되었습니다. 이 작업에는 적용되지 않습니다." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "지문" }, @@ -5537,6 +5679,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "이 기기 기억하기" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 0ee268beac9..51ca6a0f798 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -3,16 +3,19 @@ "message": "Visas lietotnes" }, "criticalApplications": { - "message": "Kritiskas lietotnes" + "message": "Kritiskās lietotnes" + }, + "accessIntelligence": { + "message": "Piekļuves inteliģence" }, "riskInsights": { - "message": "Risk Insights" + "message": "Risku ieskats" }, "passwordRisk": { "message": "Paroļu risks" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Riskam pakļautu (vāju, atklātu vai vairākkārt izmantotu) paroļu pārskatīšana dažādās lietotnēs. Jāatlasa viskritiskākās paroles, lai noteiktu svarīgas drošības darbības, ko lietotājiem pielietot riskam pakļautām parolēm." }, "dataLastUpdated": { "message": "Dati pēdējoreiz atjaunināti: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Apziņotie dalībnieki" }, + "revokeMembers": { + "message": "Atsaukt dalībniekus" + }, + "restoreMembers": { + "message": "Atjaunot dalībniekus" + }, + "cannotRestoreAccessError": { + "message": "Nevar atjaunot apvienības piekļuvi" + }, "allApplicationsWithCount": { "message": "Visas lietotnes ($COUNT$)", "placeholders": { @@ -39,7 +51,7 @@ "message": "Izveidot jaunu pieteikšanās vienumu" }, "criticalApplicationsWithCount": { - "message": "Kritiskas lietotnes ($COUNT$)", + "message": "Kritiskās lietotnes ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -75,13 +87,13 @@ "message": "Atlasi kritiskākās lietotnes, la iatklātu riskam pakļautas paroles un apziņotu lietotājus, lai tās nomaina!" }, "markCriticalApps": { - "message": "Atzīmēt kritiskas lietotnes" + "message": "Atzīmēt kritiskās lietotnes" }, "markAppAsCritical": { "message": "Atzīmēt lietotni kā kritisku" }, "appsMarkedAsCritical": { - "message": "Lietotnes, kas atzīmētas kā kritisku" + "message": "Lietotnes, kas atzīmētas kā kritiskas" }, "application": { "message": "Lietotne" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Droša piezīme" }, + "typeSshKey": { + "message": "SSH atslēga" + }, "typeLoginPlural": { "message": "Pieteikšanās vienumi" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Ir jāuzstāda pieteikšanās ar ierīci Bitwarden lietotnes iestatījumos. Nepieciešama cita iespēja?" }, + "needAnotherOptionV1": { + "message": "Nepieciešama cita iespēja?" + }, "loginWithMasterPassword": { "message": "Pieteikties ar galveno paroli" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Pieteikties Bitwarden" }, + "authenticationTimeout": { + "message": "Autentificēšanās noildze" + }, + "authenticationSessionTimedOut": { + "message": "Iestājās autentificēšanās sesijas noildze. Lūgums sākt pieteikšanos no jauna." + }, "verifyIdentity": { "message": "Jāapliecina sava identitāte" }, + "whatIsADevice": { + "message": "Kas ir ierīce?" + }, + "aDeviceIs": { + "message": "Ierīce ir atsevišķa uzstādīta Bitwarde lietotne, kurā ir veikta pieteikšanās. Atkārtota uzstādīšana, lietotnes datu vai sīkdatņu notīrīšana var beigties ar ierīces vairākkārtīgu parādīšanos." + }, "logInInitiated": { "message": "Uzsākta pieteikšanās" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Uz ierīci ir nosūtīts paziņojums." }, + "aNotificationWasSentToYourDevice": { + "message": "Uz ierīci tika nosūtīts paziņojums" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lūgums pārliecināties, ka konts ir atslēgts un atpazīšanas vārdkopa ir tāda pati arī otrā ierīcē" + }, "versionNumber": { "message": "Laidiens $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Paroles izmaiņu vēsture" }, + "generatorHistory": { + "message": "Veidotāja vēsture" + }, + "clearGeneratorHistoryTitle": { + "message": "Iztīrīt veidotāja vēsturi" + }, + "cleargGeneratorHistoryDescription": { + "message": "Turpinot visi veidotāja vēstures ieraksti tiks neatgrieziniski izdzēsti. Vai tiešām turpināt?" + }, "noPasswordsInList": { "message": "Nav paroļu, ko parādīt." }, + "clearHistory": { + "message": "Iztīrīt vēsturi" + }, + "nothingToShow": { + "message": "Nav nekā, ko parādīt" + }, + "nothingGeneratedRecently": { + "message": "Pēdējā laikā nav nekas izveidots" + }, "clear": { "message": "Notīrīt", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Lūgums pieteikties atkārtoti." }, + "currentSession": { + "message": "Pašreizējā sesija" + }, + "requestPending": { + "message": "Pieprasījums ir apstrādē" + }, "logBackInOthersToo": { "message": "Lūgums pieteikties atkārtoti. Ja tiek izmantotas citas Bitwarden lietotnes, ir nepieciešams atteikties un atkārtoti pieteikties arī tajās." }, @@ -1738,7 +1798,7 @@ "message": "Piesardzību, šīs darbības nav atsaucamas!" }, "dangerZoneDescSingular": { - "message": "Piesardzību, šī darbība ir neatgriezeniska!" + "message": "Uzmanīgi, šī darbība ir neatgriezeniska!" }, "deauthorizeSessions": { "message": "Padarīt sesijas spēkā neesošas" @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Visu sesiju darbība ir atsaukta" }, - "accountIsManagedMessage": { - "message": "Šo kontu pārvalda $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Šis konts pieder $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Tev ir atlicis 1 uzaicinājums." + }, "userUsingTwoStep": { "message": "Šis lietotājs izmanto divpakāpju pieteikšanos, lai aizsargātu savu kontu." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Skatīt visas pieteikšanās iespējas" + }, "viewAllLoginOptions": { "message": "Skatīt visas pieteikšanās iespējas" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Ierīce" }, + "loginStatus": { + "message": "Pieteikšanās stāvoklis" + }, + "firstLogin": { + "message": "Pirmā pieteikšanās" + }, + "trusted": { + "message": "Uzticama" + }, "creatingAccountOn": { "message": "Tiek veidots konts" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Atjaunināt pārlūku" }, + "generatingRiskInsights": { + "message": "Tiek veidots ieskats par riskiem..." + }, "updateBrowserDesc": { "message": "Tiek izmantots neatbalstīts tīmekļa pārlūks. Tīmekļa glabātava var nedarboties pareizi." }, + "freeTrialEndPromptCount": { + "message": "Bezmaksas izmēģinājums beigsies pēc $COUNT$ dienām.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$ bezmaksas izmēģinājums beigsies pēc $COUNT$ dienām.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$ bezmaksas izmēģinājums beigsies rīt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Bezmaksas izmēģinājums beigsies rīt." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$ bezmaksas izmēģinājums beigsies šodien.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Bezmaksas izmēģinājums beigsies šodien." + }, + "clickHereToAddPaymentMethod": { + "message": "Klikšķināt šeit, lai pievienotu maksājuma veidu." + }, "joinOrganization": { "message": "Pievienoties apvienībai" }, @@ -4388,6 +4515,9 @@ "message": "Vairs nevaicāt pārbaudīt atpazīšanas vārdkopu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Tiks paziņots, tiklīdz pieprasījums būs apstiprināts" + }, "free": { "message": "Bezmaksas", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Pieteikties no apvienības vienotās pieteikšanās portāla. Lūgums ievadīt apvienības identifikatoru, lai sāktu." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Jāievada savas apvienības SSO identifikators, lai sāktu" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Lai pieteiktos ar savu SSO nodrošinātāju, jāievada savas apvienības SSO identifikators, lai sāktu. Var būt nepieciešams ievadīt šo SSO identifikatoru, kad noteik pieteikšanās jaunā ierīcē." + }, "enterpriseSingleSignOn": { "message": "Uzņēmuma vienotā pieteikšanās" }, @@ -4695,7 +4831,7 @@ "message": "Apvienības dalībnieki, kas nav īpašnieki vai pārvaldītāji un jau ir dalībnieki citā apvienībā, tiks atbrīvoti." }, "singleOrgPolicyMemberWarning": { - "message": "Pamatnostādnei neatbilstoši dalībniekiem tiks piešķirts stāvoklis \"Atsaukts\", līdz viņī pametīs visas pārējās apvienības. Pārvaldītāji ir izņēmums, un viņi var atjaunot dalībniekus, tiklīdz ir sasniegta atbilstība pamatnostādnei." + "message": "Pamatnostādnei neatbilstoši dalībniekiem tiks piešķirts stāvoklis \"Atsaukts\", līdz viņī pametīs visas pārējās apvienības. Pārvaldītāji ir izņēmums, un viņi var atjaunot dalībniekus, tiklīdz ir īstenota atbilstība pamatnostādnei." }, "requireSso": { "message": "Pieprasīt vienotās pieteikšanās autentificēšanos" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Nav iekļauts, tādēļ nav piemērojams šai darbībai." }, + "nonCompliantMembersTitle": { + "message": "Neatbilstoši dalībnieki" + }, + "nonCompliantMembersError": { + "message": "Dalībniekus, kuri neatbilst viena uzņēmuma vai divpakāpju pieteikšanās nosacījumam, nevar atjaunot, līdz tie ievēros nosacījumu prasības" + }, "fingerprint": { "message": "Pirkstu nospiedums" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Lietotāju pārvaldīšanai ir jābūt iespējotai arī ar konta atkopšanas pārvaldīšanas atļauju" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-pasts nosūtīts" }, - "revokeSponsorshipConfirmation": { - "message": "Pēc šī konta noņemšanas, ģimenes apvienības īpašnieks būsi atbildīgs par šo abonementu un saistītajiem rēķiniem. Vai tiešām turpināt?" - }, "removeSponsorshipSuccess": { "message": "Noņemta pabalstītājdarbība" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Nepieciešams, ja vienības Id nav URL." }, + "offerNoLongerValid": { + "message": "Šis piedāvājums vairs nav derīgs. Jāsazinās ar savas apvienības pārvaldītājiem, lai iegūtu vairāk informācijas." + }, "openIdOptionalCustomizations": { "message": "Papildu pielāgojumi" }, @@ -6411,10 +6567,10 @@ "message": "Izveidot lietotājvārdu" }, "generateEmail": { - "message": "Izveidot e-pastu" + "message": "Izveidot e-pasta adresi" }, - "generatorBoundariesHint": { - "message": "Vērtībai jābūt starp $MIN$ un $MAX$", + "spinboxBoundariesHint": { + "message": "Vērtībai jābūt starp $MIN$ un $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Jāizmanto $RECOMMENDED$ vai vairāk rakstzīmju, la izveidotu spēcīgu paroli.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Jāizmanto $RECOMMENDED$ vai vairāk vārdu, lai aizveidotu spēcīgu paroles vārdkopu.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Lietotājvārda veids" }, @@ -6681,6 +6857,10 @@ "message": "Automātiski nodrošina lietotājus un kopas ar vēlamo identitātes nodrošinātāju, izmantojot SCIM nodrošināšanu", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automātiski nodrošina lietotājus un kopas ar vēlamo identitāšu nodrošinātāju, izmantojot SCIM nodrošināšanu. Atrast atbalstītās integrācijas", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Iespējot SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Uzsākta pieteikšanās" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Atcerēties šo ierīci, lai nākotnes pieteikšanos padarītu plūdenāku" + }, "deviceApprovalRequired": { "message": "Nepieciešams ierīces apstiprinājums. Zemāk jāatlasa apstiprinājuma iespēja:" }, + "deviceApprovalRequiredV2": { + "message": "Nepieciešama ierīces apstiprināšana" + }, + "selectAnApprovalOptionBelow": { + "message": "Zemāk jāatlasa apstiprināšnas iespēja" + }, "rememberThisDevice": { "message": "Atcerēties šo ierīci" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Apstiprināt pieprasījumu" }, + "deviceApproved": { + "message": "Ierīce apstiprināta" + }, + "deviceRemoved": { + "message": "Ierīce noņemta" + }, + "removeDevice": { + "message": "Noņemt ierīci" + }, + "removeDeviceConfirmation": { + "message": "Vai tiešām noņemt šo ierīci?" + }, "noDeviceRequests": { "message": "Nav ierīču pieprasījumu" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Trūkst lietotāja e-pasta adreses" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktīva lietotāja e-pasta adrese netika atrasta. Notiek atteikšanās." + }, "deviceTrusted": { "message": "Ierīce ir uzticama" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Krājumu uzvedības pārvaldība apvienībā" }, - "limitCollectionCreationDeletionDesc": { - "message": "Ļaut krājumu izveidošanu un izdzēšanu tikai īpašniekiem un pārvaldniekiem" - }, "limitCollectionCreationDesc": { "message": "Ļaut krājumu izveidošanu tikai īpašniekiem un pārvaldītājiem" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "jāpievieno maksājuma veids", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Apvienības informācija" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Bitwarden Noslēpumu pārvaldnieka izstrādātāju rīkkopa ir izmantojama ar zemāk esošajām programmēšanas valodām, lai veidotu pats savas lietotnes." }, - "setUpGithubActions": { - "message": "Iestatīt GitHub darbības" + "ssoDescStart": { + "message": "Konfigurēt", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "Bitwarden, izmantojot sava identitāšu nodrošinātāja ieviešanas norādes.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Lietotāju nodrošināšana" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Konfigurēt ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Iestatīt Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management), lai automātiski nodrošinātu lietotājus un kopas Bitwarden, izmantojot sava identitāšu nodrošinātāja ieviešanas norādes.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Iestatīt GitLab CI/CD" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Iestatīt Ansible" + "bwdcDesc": { + "message": "Konfigurēt Bitwarden Directory Connector, lai automātiski nodrošinātu lietotājus un kopas, izmantojot sava identitāšu nodrošinātāja ieviešanas norādes." }, - "rustSDKRepo": { - "message": "Skatīt Rust glabātavu" + "eventManagement": { + "message": "Notikumu pārvaldība" }, - "cSharpSDKRepo": { - "message": "Skatīt C# glabātavu" + "eventManagementDesc": { + "message": "Iekļaut Bitwarden notikumu žurnālus savā SIEM (System Information and Event Management) sistēmā, izmantojot operētājsistēmai atbilstošas ieviešanas norādes." }, - "cPlusPlusSDKRepo": { - "message": "Skatīt C++ glabātavu" + "deviceManagement": { + "message": "Ierīču pārvaldība" }, - "jsWebAssemblySDKRepo": { - "message": "Skatīt JS WebAssembly glabātavu" + "deviceManagementDesc": { + "message": "Konfigurēt ierīču pārvaldību Bitwarden, izmantojot operētājsistēmai atbilstošas ieviešanas norādes." }, - "javaSDKRepo": { - "message": "Skatīt Java glabātavu" + "integrationCardTooltip": { + "message": "Palaist $INTEGRATION$ ieviešanas norādes.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Uzstādīt $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Skatīt Python glabātavu" + "smSdkTooltip": { + "message": "Skatīt $SDK$ glabātavu", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Skatīt PHP glabātavu" + "integrationCardAriaLabel": { + "message": "atvērt $INTEGRATION$ ieviešanas norādes jaunā cilnē.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Skatīt Ruby glabātavu" + "smSdkAriaLabel": { + "message": "skatīt $SDK$ glabātavu jaunā cilnē.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Skatīt Go glabātavu" + "smIntegrationCardAriaLabel": { + "message": "uzstādīt $INTEGRATION$ ieviešanas norādes jaunā cilnē.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Izveidot jaunu klienta apvienību, ko pārvaldīt kā nodrošinātājam. Papildu vietas tiks atspoguļotas nākamajā norēķinu posmā." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Pārvaldīts pakalpojuma nodrošinātājs" }, + "managedServiceProvider": { + "message": "Pārvaldīts pakalpojuma nodrošinātājs" + }, + "multiOrganizationEnterprise": { + "message": "Daudzapvienību uzņēmums" + }, "orgSeats": { "message": "Apvienības vietas" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Atjaunināta nodokļu informācija" }, + "billingInvalidTaxIdError": { + "message": "Nederīgs nodokļu identifikators. Ja ir pārliecība, ka tā ir kļūda, lūgums sazināties ar atbalstu." + }, + "billingTaxIdTypeInferenceError": { + "message": "Mēs nevarējām pārbaudīt nodokļu identifikatoru. Ja ir pārliecība, ka tā ir kļūda, lūgums sazināties ar atbalstu." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Nederīgs nodokļu identifikators. Ja ir pārliecība, ka tā ir kļūda, lūgums sazināties ar atbalstu." + }, + "billingPreviewInvoiceError": { + "message": "Rēķina priekšskatīšanas laikā atgadījās kļūda. Lūgums vēlāk mēģināt vēlreiz." + }, "unverified": { "message": "Neapliecināts" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB papildu krātuves" }, + "sshKeyAlgorithm": { + "message": "Atslēgas algoritms" + }, + "sshKeyFingerprint": { + "message": "Pirkstu nospiedums" + }, + "sshKeyPrivateKey": { + "message": "Privātā atslēga" + }, + "sshKeyPublicKey": { + "message": "Publiskā atslēga" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 \"Premium\" konti" }, @@ -9547,10 +9845,10 @@ "message": "Lai mitinātu Bitwarden savā serverī, ir nepieciešams augšupielādēt licences datni. Lai nodrošinātu bezmaksas ģimeņu plānus un papildu norēķinu iespējas pašmitinātajai apvienībai, tajā ir nepieciešams uzstādīt automātisku sinhronizēšanu." }, "selfHostingTitleProper": { - "message": "Pašmitināts" + "message": "Pašmitināšana" }, - "verified-domain-single-org-warning": { - "message": "Domēna apliecināšana ieslēgts vienas apvienības pamatnostādni." + "claim-domain-single-org-warning": { + "message": "Domēna pieteikšana ieslēgs vienas apvienības pamatnostādni." }, "single-org-revoked-user-warning": { "message": "Pamatnostādnei neatbilstošie dalībnieki tiks atsaukti. Pārvaldītāji var atjaunot dalībniekus, tiklīdz viņi pametīs visas pārējās apvienības." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Kad dalībnieks tiek izdzēsts, viņa Bitwarden konts un personīgās glabātavas dati tiks neatgriezeniski izdzēsti. Krājumu dati paliks apvienībām. Lai atjaunotu dalībniekus, viņiem atkārtoti jāizveido konts un jāpievieno komandai.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Šī darbība neatgriezeniski izdzēsīs visus $NAME$ piederošos vienumus. Krājumu vienumi netiks ietekmēti.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Šī darbība neatgriezeniski izdzēsīs visus zemāk esošajiem dalībniekiem piederošos vienumus. Krājumu vienumi netiks ietekmēti.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Izdzēsts/a $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Lietotājs tika noņemts no apvienības, un visa saistītā lietotāja informācija tika izdzēsta." + }, + "deletedUserId": { + "message": "Izdzēsts lietotājs $ID$ - īpašnieks/pārvaldītājs izdzēsa lietotāja kontu", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Lietotājs $ID$ pameta apvienību", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ ir apturēta", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Jāsazinās ar savas apvienības īpašnieku, lai iegūtu palīdzību." + }, + "suspendedOwnerOrgMessage": { + "message": "Lai atkārtoti iegūtu piekļuvi savai apvienībai, jāpievieno apmaksas veids." + }, + "deleteMembers": { + "message": "Izdzēst dalībiniekus" + }, + "noSelectedMembersApplicable": { + "message": "Šī darbība nav attiecināma uz nevienu no atlasītajiem dalībniekiem." + }, + "deletedSuccessfully": { + "message": "Veiksmīgi izdzēsts" + }, + "freeFamiliesSponsorship": { + "message": "Noņemt bezmaksas Bitwarden ģimeņu pabalstītājdarbību" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Neļaut dalībniekiem iegūt plānu \"Ģimenes\" caur šo apvienību." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Apmaksa ar bankas kontu ir pieejama tikai klientiem Savienotajās Valstīs. Būs nepieciešams apliecināt savu bankas kontu. Mēs veiksim sīkiemaksu nākamās darba dienas vai divu laikā. Pēc tam šīs iemaksas uzdevuma aprakstā esošais kods būs jāievada apvienības apmaksas lapā, lai apliecinātu bankas kontu. Bankas konta apliecināšanas neveikšana beigsies ar nokavētu maksājumu un apturētu abonementu." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Mēs veicām sīkiemaksu Tavā bankas kontā (tas var aizņemt 1 līdz 2 darba dienas). Jāievada iemaksas aprakstā atrodamais sešu ciparu kods, kas sākas ar \"SM\". Bankas konta apliecināšans neveikšana beigsies ar nokavētu maksājumu un apturētu abonementu." + }, + "descriptorCode": { + "message": "Apraksta kods" + }, + "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" + }, + "removeMembers": { + "message": "Noņemt dalībniekus" + }, + "devices": { + "message": "Ierīces" + }, + "deviceListDescription": { + "message": "Kontā ir notikusi pieteikšanās katrā no zemāk uzskaitītajām ierīcēm. Ja kāda no tām nav atpazīstama, tā ir uzreiz jānoņem." + }, + "deviceListDescriptionTemp": { + "message": "Ar kontu ir veikta pieteikšanās katrā no zemāk uzskaitītajām ierīcēm." + }, + "claimedDomains": { + "message": "Pieteiktie domēni" + }, + "claimDomain": { + "message": "Pieteikt domēnu" + }, + "reclaimDomain": { + "message": "Atkārtoti pieteikt domēnu" + }, + "claimDomainNameInputHint": { + "message": "Piemērs: mansdomens.lv. Apakšdomēnu pieteikšanai ir nepieciešami atsevišķi ieraksti." + }, + "automaticClaimedDomains": { + "message": "Automātiski pieteiktie domēni" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden mēģinās pārbaudīt domēnu 3 reizes pirmajās 72 stundās. Ja domēnu nevarēs pieteikt, būs jāpārbauda DNS ieraksts saimniekdatorā un tas pašrocīgi jāpiesaka. Domēns tiks noņemts no apvienības pēc 7 dienām, ja tas nebūs pieteikts." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ nav pieteikts. Jāpārbauda DNS ieraksts.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Pieteikts" + }, + "domainStatusUnderVerification": { + "message": "Apliecināšanā" + }, + "claimedDomainsDesc": { + "message": "Pieteikt domēnu, lai iegūtu visu dalībnieku kontu, kuru e-pasta adrese atbilst domēnam, īpašumtiesības. Dalībnieki piesakoties varēs izlaist SSO identifikatoru. Pārvaldītāji varēs arī izdzēst dalībnieku kontus." + }, + "invalidDomainNameClaimMessage": { + "message": "Ievadītā vērtība ir nederīga. Piemēram: mansdomens.lv. Apakšdomēnu pieteikšanai ir nepieciešami atsevišķi ieraksti." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ pieteikts", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ nav pieteikts", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domēns pieteikts" + }, + "organizationNameMaxLength": { + "message": "Apvienības nosaukums nevar pārsniegt 50 rakstzīmes." + }, + "resellerRenewalWarning": { + "message": "Abonements drīz tiks atjaunots. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $RENEWAL_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Rēķins par abonementu tika izdots $ISSUED_DATE$. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $DUE_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Rēķins par abonementu nav apmaksāts. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $GRACE_PERIOD_END$ jāsazināš ar $RESELLER$, lai apstiprinātu atjaunošanu.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Apvienības abonements atsākts" + }, + "restartSubscription": { + "message": "Atsākt savu abonementu" + }, + "suspendedManagedOrgMessage": { + "message": "Jāsazinās ar $PROVIDER$, lai iegūtu palīdzību.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 7a71b4b07c9..a6eb0e474fd 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "സുരക്ഷിത കുറിപ്പ്" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "വേർഷൻ $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "പാസ്സ്‌വേഡ് ചരിത്രം" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "പ്രദർശിപ്പിക്കാൻ പാസ്സ്‌വേഡുകൾ ഒന്നും ഇല്ല." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "മായ്ക്കുക", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "ദയവായി തിരികെ പ്രവേശിക്കുക." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "എല്ലാ സെഷനും നിരസിച്ചു." }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "ഈ ഉപയോക്താവ് അവരുടെ അക്കൗണ്ട് രണ്ട്-പ്രവേശനം ഉപയോഗിച്ച് സുരക്ഷിതമാക്കിയിരിക്കുന്നു." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "ഉപകരണം" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "ബ്രൌസർ അപ്‌ഡേറ്റുചെയ്യുക" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "ഓർഗനൈസേഷനിൽ ചേരുക" }, @@ -4388,6 +4515,9 @@ "message": "ഫിംഗർപ്രിന്റ് വാചകം പരിശോധിക്കാൻ ആവശ്യപ്പെടരുത്", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "സൗജന്യം ", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "നിങ്ങളുടെ സംഘടനയുടെ സിംഗിൾ സൈൻ-ഓൺ പോർട്ടൽ ഉപയോഗിച്ച് വേഗത്തിൽ ലോഗിൻ ചെയ്യുക. ആരംഭിക്കുന്നതിന് ദയവായി നിങ്ങളുടെ സംഘടനയുടെ ഐഡന്റിഫയർ നൽകുക." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "എന്റർപ്രൈസ് SSO" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 57cc9a404e1..e6cce3405d5 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 57cc9a404e1..e6cce3405d5 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 59e0334fde4..da80546c9a5 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Sikkert notat" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Innlogginger" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Logg på med enhet må settes opp i Bitwarden-innstillingene. Trenger du et annet alternativ?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Logg på med hovedpassord" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Pålogging startet" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Et varsel har blitt sendt til enheten din." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versjon $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Passordhistorikk" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Det er ingen passord å liste opp." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Tøm", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Vennligst logg på igjen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vennligst logg inn på nytt. Dersom du bruker andre Bitwarden-applikasjoner logg av og på på dem også." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Fjernet autoriseringen fra alle økter" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Denne brukeren bruker 2-trinnsinnlogging til å beskytte kontoen sin." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Vis alle påloggingsalternativer" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Oppdater nettleseren" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Du bruker en ustøttet nettleser. Netthvelvet vil kanskje ikke fungere ordentlig." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Bli med i organisasjon" }, @@ -4388,6 +4515,9 @@ "message": "Ikke be om bekreftelse av fingeravtrykksfrase flere ganger", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Logg inn ved hjelp av din organisasjons eneste signalportal. Angi din organisasjons identifikator for å begynne." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Bedriftsinnlogging (SSO)" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Unntatt dette, gjelder ikke for dette tiltaket." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingeravtrykk" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-post sendt" }, - "revokeSponsorshipConfirmation": { - "message": "Etter at kontoen er fjernet, vil Familier-organisasjonens eier bli ansvarlig for dette abonnementet og relaterede fakturaer. Er du sikker på at du vil fortsette?" - }, "removeSponsorshipSuccess": { "message": "Sponsor fjernet" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Kreves hvis enhets-ID ikke er en URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Valgfrie tilpasninger" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Brukernavntype" }, @@ -6681,6 +6857,10 @@ "message": "Genererer brukere og grupper automatisk med den foretrukne identitetstjenesten din via SCIM-klargjøring", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Skru på SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 5bcb5fb6c24..7ebca5938e0 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 7595736573c..b38e6f4c3c3 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Belangrijke applicaties" }, + "accessIntelligence": { + "message": "Toegangsintelligentie" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Risicoinzichten" }, "passwordRisk": { "message": "Wachtwoordrisico" }, - "discoverAtRiskPasswords": { - "message": "Ontdek risicovolle wachtwoorden en attendeer gebruikers deze wachtwoorden te wijzigen." + "reviewAtRiskPasswords": { + "message": "Herzie risicovolle wachtwoorden (zwak, blootgelegd of herbruikt) tussen applicaties. Selecteer je meest belangrijke applicaties om prioriteit te geven aan beveiligingsacties voor je gebruikers om risicovolle wachtwoorden aan te pakken." }, "dataLastUpdated": { "message": "Datum laatste wijziging: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Geînformeerde leden" }, + "revokeMembers": { + "message": "Leden intrekken" + }, + "restoreMembers": { + "message": "Leden herstellen" + }, + "cannotRestoreAccessError": { + "message": "Kan organisatietoegang niet herstellen" + }, "allApplicationsWithCount": { "message": "Alle applicaties ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Veilige notitie" }, + "typeSshKey": { + "message": "SSH-sleutel" + }, "typeLoginPlural": { "message": "Logins" }, @@ -937,7 +952,7 @@ "message": "Uitgelogd" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Je bent afgemeld bij je account." }, "loginExpired": { "message": "Je inlogsessie is verlopen." @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Je moet Inloggen met apparaat instellen in de instellingen van de Bitwarden-app. Behoefte aan een andere mogelijkheid?" }, + "needAnotherOptionV1": { + "message": "Nog een optie nodig?" + }, "loginWithMasterPassword": { "message": "Inloggen met je hoofdwachtwoord" }, @@ -1101,8 +1119,20 @@ "logInToBitwarden": { "message": "Inloggen op Bitwarden" }, + "authenticationTimeout": { + "message": "Authenticatie-timeout" + }, + "authenticationSessionTimedOut": { + "message": "De verificatiesessie is verlopen. Start het inlogproces opnieuw op." + }, "verifyIdentity": { - "message": "Verify your Identity" + "message": "Controleer je identiteit" + }, + "whatIsADevice": { + "message": "Wat is een apparaat?" + }, + "aDeviceIs": { + "message": "Een apparaat is een unieke installatie van de Bitwarden-app waar je bent ingelogd. Het opnieuw installeren, verwijderen van app-gegevens of het wissen van uw cookies kan zorgen voor het meerdere keren weergeven van dat apparaat." }, "logInInitiated": { "message": "Inloggen gestart" @@ -1230,7 +1260,7 @@ "message": "E-mailadres" }, "yourVaultIsLockedV2": { - "message": "Je kluis is vergrendeld." + "message": "Je kluis is vergrendeld" }, "yourAccountIsLocked": { "message": "Je account is vergrendeld" @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Er is een bericht naar je apparaat verstuurd." }, + "aNotificationWasSentToYourDevice": { + "message": "Er is een melding naar je apparaat verzonden" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Zorg ervoor dat je kluis is ontgrendeld en de vingerafdrukzin hetzelfde is op het andere apparaat" + }, "versionNumber": { "message": "Versie $VERSION_NUMBER$", "placeholders": { @@ -1420,7 +1456,7 @@ "message": "Wijzig de verzamelingen waarmee dit item gedeeld is. Alleen organisatiegebruikers met toegang tot deze verzamelingen kunnen dit item inzien." }, "deleteSelectedItemsDesc": { - "message": "Je hebt $COUNT$ item(s) geselecteerd om te verwijderen. Weet je zeker dat je al deze items wilt verwijderen?", + "message": "$COUNT$ item(s) worden naar de prullenbak gestuurd.", "placeholders": { "count": { "content": "$1", @@ -1441,7 +1477,7 @@ "message": "Weet je zeker dat je wilt doorgaan?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Kies een map waaraan je de $COUNT$ geselecteerde item(s) wilt toevoegen.", "placeholders": { "count": { "content": "$1", @@ -1476,10 +1512,10 @@ "message": "UUID kopiëren" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Fout bij vernieuwen toegangstoken" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Geen verversingstoken of API-sleutels gevonden. Probeer uit te loggen en weer in te loggen." }, "warning": { "message": "Waarschuwing" @@ -1560,7 +1596,7 @@ "message": "Dit bestand is beveiligd met een wachtwoord. Voer het bestandswachtwoord in om gegevens te importeren." }, "exportSuccess": { - "message": "Je kluisgegevens zijn geëxporteerd." + "message": "Kluisgegevens geëxporteerd" }, "passwordGenerator": { "message": "Wachtwoordgenerator" @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Geschiedenis" }, + "generatorHistory": { + "message": "Generatorgeschiedenis" + }, + "clearGeneratorHistoryTitle": { + "message": "Generatorgeschiedenis wissen" + }, + "cleargGeneratorHistoryDescription": { + "message": "Als je doorgaat, wis je definitief de geschiedenis van de generator. Weet je zeker dat je wilt doorgaan?" + }, "noPasswordsInList": { "message": "Er zijn geen wachtwoorden om weer te geven." }, + "clearHistory": { + "message": "Geschiedenis wissen" + }, + "nothingToShow": { + "message": "Niets weer te geven" + }, + "nothingGeneratedRecently": { + "message": "Je hebt de laatste tijd niets gegenereerd" + }, "clear": { "message": "Wissen", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Log opnieuw in." }, + "currentSession": { + "message": "Huidige sessie" + }, + "requestPending": { + "message": "Verzoek in behandeling" + }, "logBackInOthersToo": { "message": "Svp opnieuw inloggen. Als je andere Bitwarden-applicaties gebruikt, dan moet je daar ook uit- en inloggen." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Autorisatie van alle sessies ingetrokken" }, - "accountIsManagedMessage": { - "message": "Dit account wordt beheerd door $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Dit account is eigendom van $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1822,11 +1882,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { - "message": " aanmaken.", + "message": " in plaats daarvan.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." }, "onboardingImportDataDetailsPartTwoWithOrgs": { - "message": " aanmaken. Je moet misschien wachten tot je beheerder je organisatielidmaatschap bevestigt.", + "message": " in plaats daarvan. Je moet misschien wachten tot je beheerder je organisatielidmaatschap bevestigt.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -1836,7 +1896,7 @@ "message": "Er was een probleem met de data die je probeerde te importeren. Los de onderstaande fouten op in het bronbestand en probeer het opnieuw." }, "importSuccess": { - "message": "De gegevens zijn in je kluis geïmporteerd." + "message": "Gegevens succesvol geïmporteerd" }, "importSuccessNumberOfItems": { "message": "Een totaal van $AMOUNT$ items zijn geïmporteerd.", @@ -2704,7 +2764,7 @@ "message": "Weet je zeker dat je wilt opzeggen? Je verliest toegang tot alle functionaliteiten van dit abonnement aan het einde van deze betalingscyclus." }, "canceledSubscription": { - "message": "Het abonnement is opgezegd." + "message": "Abonnement geannuleerd" }, "neverExpires": { "message": "Vervalt nooit" @@ -3090,7 +3150,7 @@ "message": "Je nieuwe organisatie is klaar voor gebruik!" }, "organizationUpgraded": { - "message": "Je organisatie is bijgewerkt." + "message": "Organisatie bijgewerkt" }, "leave": { "message": "Verlaten" @@ -3099,7 +3159,7 @@ "message": "Weet je zeker dat je deze organisatie wilt verlaten?" }, "leftOrganization": { - "message": "Je hebt de organisatie verlaten." + "message": "Je hebt de organisatie verlaten" }, "defaultCollection": { "message": "Standaardverzameling" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Je hebt 1 uitnodiging over." + }, "userUsingTwoStep": { "message": "Het account van deze gebruiker is beschermd met tweestapsaanmelding." }, @@ -3237,7 +3300,7 @@ "message": "Eigenaar" }, "ownerDesc": { - "message": "De gebruiker met de hoogste toegangsrechten. Deze gebruiker kan alle aspecten van je organisatie beheren." + "message": "De gebruiker met de hoogste toegangsrechten. Deze gebruiker kan alle aspecten van je organisatie beheren" }, "clientOwnerDesc": { "message": "Deze gebruiker moet onafhankelijk zijn van de provider. Als de provider is losgekoppeld van de organisatie, blijft deze gebruiker eigenaar van de organisatie." @@ -3246,22 +3309,22 @@ "message": "Beheerder" }, "adminDesc": { - "message": "Beheerders hebben toegang tot alle items, verzamelingen en gebruikers binnen je organisatie en kunnen deze ook beheren." + "message": "Beheerders hebben toegang tot alle items, verzamelingen en gebruikers binnen je organisatie en kunnen deze ook beheren" }, "user": { "message": "Gebruiker" }, "userDesc": { - "message": "Een standaardgebruiker met toegang tot de verzamelingen van je organisatie." + "message": "Items openen en toevoegen aan toegewezen collecties" }, "all": { "message": "Alle" }, "addAccess": { - "message": "Add Access" + "message": "Toegang toevoegen" }, "addAccessFilter": { - "message": "Add Access Filter" + "message": "Toegangsfilter toevoegen" }, "refresh": { "message": "Verversen" @@ -3303,16 +3366,16 @@ "message": "Bitwarden Secrets Manager" }, "loggedIn": { - "message": "Ingelogd." + "message": "Ingelogd" }, "changedPassword": { - "message": "Accountwachtwoord veranderd." + "message": "Accountwachtwoord veranderd" }, "enabledUpdated2fa": { - "message": "Tweestapsaanmelding geactiveerd/bijgewerkt." + "message": "Inloggen in twee stappen opgeslagen" }, "disabled2fa": { - "message": "Tweestapsaanmelding uitgeschakeld." + "message": "Inloggen in twee stappen uitgeschakeld" }, "recovered2fa": { "message": "Account hersteld van tweestapsaanmelding." @@ -3337,7 +3400,7 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { - "message": "Kluis geëxporteerd." + "message": "Kluis geëxporteerd" }, "exportedOrganizationVault": { "message": "Organisatiekluis geëxporteerd." @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Alle inlogopties bekijken" + }, "viewAllLoginOptions": { "message": "Alle loginopties bekijken" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Apparaat" }, + "loginStatus": { + "message": "Loginstatus" + }, + "firstLogin": { + "message": "Eerst inloggen" + }, + "trusted": { + "message": "Vertrouwd" + }, "creatingAccountOn": { "message": "Account maken bij" }, @@ -3757,7 +3832,7 @@ "message": "Wijzig de groep waar deze gebruiker bij hoort." }, "invitedUsers": { - "message": "Gebruiker(s) uitgenodigd." + "message": "Gebruiker(s) uitgenodigd" }, "resendInvitation": { "message": "Uitnodiging opnieuw versturen" @@ -3766,7 +3841,7 @@ "message": "E-mail opnieuw versturen" }, "hasBeenReinvited": { - "message": "$USER$ is opnieuw uitgenodigd.", + "message": "$USER$ opnieuw uitgenodigd", "placeholders": { "user": { "content": "$1", @@ -3814,7 +3889,7 @@ "message": "Kijk in het postvak IN van je e-mail voor een verificatielink." }, "emailVerified": { - "message": "Je e-mailadres is geverifieerd." + "message": "Account e-mail geverifieerd" }, "emailVerifiedV2": { "message": "E-mailadres geverifieerd" @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Webbrowser bijwerken" }, + "generatingRiskInsights": { + "message": "Je risico-inzichten genereren..." + }, "updateBrowserDesc": { "message": "Je maakt gebruik van webbrowser die we niet ondersteunen. De webkluis werkt mogelijk niet goed." }, + "freeTrialEndPromptCount": { + "message": "Je gratis proefperiode eindigt over $COUNT$ dagen.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, je gratis proefperiode eindigt over $COUNT$ dagen.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, je gratis proefperiode eindigt morgen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Je gratis proefperiode eindigt morgen." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, je gratis proefperiode eindigt vandaag.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Je gratis proefperiode eindigt vandaag." + }, + "clickHereToAddPaymentMethod": { + "message": "Klik hier voor het toevoegen van een betaalmethode." + }, "joinOrganization": { "message": "Lid worden van organisatie" }, @@ -3986,7 +4113,7 @@ "message": "Als je de bankrekening niet verifieert mis je een betaling waardoor je abonnement wordt uitgeschakeld." }, "verifiedBankAccount": { - "message": "Bankrekening geverifieerd." + "message": "Bankrekening geverifieerd" }, "bankAccount": { "message": "Bankrekening" @@ -4078,10 +4205,10 @@ "message": "Aanpassingen aan je abonnement leiden tot evenredige wijzigingen in je factuurtotaal. Als nieuwe gebruikers je gebruikersplaatsen overschrijden, ontvang je onmiddellijk een afschrijving voor de extra gebruikers." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "Als je aanvullende" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "plaatsen zonder de gebundelde aanbieding, neem dan contact op met" }, "subscriptionUserSeatsLimitedAutoscale": { "message": "Aanpassingen aan je abonnement leiden tot evenredige wijzigingen in je factuurtotaal. Als nieuwe gebruikers je gebruikersplaatsen overschrijden, ontvang je onmiddellijk een afschrijving voor de extra gebruikers tot het aantal van $MAX$ gebruikersplaatsen is bereikt.", @@ -4291,7 +4418,7 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "Organisatie uitgeschakeld." + "message": "Organisatie opgeschort" }, "secretsAccessSuspended": { "message": "Opgeschorte organisaties zijn niet toegankelijk. Neem contact op met de eigenaar van je organisatie voor hulp." @@ -4388,6 +4515,9 @@ "message": "Vraag nooit om de vingerafdrukzin te controleren voor uitgenodigde leden (niet aanbevolen)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Je krijgt een melding zodra de aanvraag is goedgekeurd" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4556,7 +4686,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Definitief verwijderd item $ID$.", + "message": "Item $ID$ permanent verwijderd", "placeholders": { "id": { "content": "$1", @@ -4577,7 +4707,7 @@ "message": "Herstelde items" }, "restoredItemId": { - "message": "Hersteld item $ID$.", + "message": "Item $ID$ hersteld", "placeholders": { "id": { "content": "$1", @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Inloggen met het single sign-on portaal van je organisatie. Voer de identificatie van je organisatie in om te beginnen." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Voer het SSO-nummer van je organisatie in om te beginnen" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Voer het SSO-nummer van je organisatie in om in te loggen met je SSO-provider. Mogelijk moet je het SSO-nummer invoeren als je op een nieuw apparaat inlogt." + }, "enterpriseSingleSignOn": { "message": "Enterprise Single Sign-On" }, @@ -4990,7 +5126,7 @@ } }, "emergencyApproved": { - "message": "Noodtoegang goedgekeurd." + "message": "Noodtoegang goedgekeurd" }, "emergencyRejected": { "message": "Noodtoegang afgewezen" @@ -5082,7 +5218,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseLink": { - "message": "Enterprise feature", + "message": "enterprise functie", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseEnd": { @@ -5421,7 +5557,7 @@ "message": "Wachtwoord opnieuw ingesteld!" }, "resetPasswordEnrollmentWarning": { - "message": "Inschrijving stelt organisatiebeheerders in staat om je hoofdwachtwoord te wijzigen. Weet je zeker dat je wilt inschrijven?" + "message": "Registratie geeft organisatiebeheerders de mogelijkheid om je hoofdwachtwoord te wijzigen" }, "accountRecoveryPolicy": { "message": "Accountherstel-administratie" @@ -5505,10 +5641,10 @@ "message": "Bulkactie status" }, "bulkConfirmMessage": { - "message": "Succesvol bevestigd." + "message": "Succesvol bevestigd" }, "bulkReinviteMessage": { - "message": "Succesvol opnieuw uitgenodigd." + "message": "Succesvol opnieuw uitgenodigd" }, "bulkRemovedMessage": { "message": "Succesvol verwijderd" @@ -5520,7 +5656,13 @@ "message": "Toegang tot de organisatie hersteld" }, "bulkFilteredMessage": { - "message": "Uitgezonderd, niet van toepassing voor deze actie." + "message": "Uitgesloten, niet van toepassing op deze actie" + }, + "nonCompliantMembersTitle": { + "message": "Niet-conforme leden" + }, + "nonCompliantMembersError": { + "message": "Leden die niet-conform zijn aan het enkele organisatie- of tweestapsaanmeldingsbeleid kunnen niet worden hersteld totdat ze voldoen aan de beleidsvereisten" }, "fingerprint": { "message": "Vingerafdruk" @@ -5537,6 +5679,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gebruikers beheren moet je ook accountherstel beheren toekennen" }, @@ -5553,7 +5709,7 @@ "message": "Providernaam" }, "providerSetup": { - "message": "De provider is ingesteld." + "message": "Provider succesvol ingesteld" }, "clients": { "message": "Apparaten" @@ -5639,7 +5795,7 @@ } }, "providerIsDisabled": { - "message": "Provider is uitgeschakeld." + "message": "Aanbieder geschorst" }, "providerUpdated": { "message": "Provider bijgewerkt" @@ -5717,7 +5873,7 @@ "message": "Minuten" }, "vaultTimeoutPolicyInEffect": { - "message": "Het beleid van je organisatie heeft invloed op de time-out van je kluis. De maximaal toegestane time-out voor je kluis is $HOURS$ uur en $MINUTES$ minuten", + "message": "Het beleid van je organisatie heeft invloed op de time-out van je kluis. De maximaal toegestane time-out voor je kluis is $HOURS$ uur en $MINUTES$ minuten.", "placeholders": { "hours": { "content": "$1", @@ -5919,7 +6075,7 @@ "message": "Onderteken authenticatie aanvragen" }, "ssoSettingsSaved": { - "message": "Single Sign-On configuratie is opgeslagen." + "message": "Single sign-on configuratie opgeslagen" }, "sponsoredFamilies": { "message": "Gratis Bitwarden Families" @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-mail verzonden" }, - "revokeSponsorshipConfirmation": { - "message": "Als je dit account verwijderd is de eigenaar van de Families-organisatie verantwoordelijk voor dit abonnement en de bijbehorende facturen. Weet je zeker dat je wilt doorgaan?" - }, "removeSponsorshipSuccess": { "message": "Sponsoring verwijderd" }, @@ -6081,7 +6234,7 @@ "message": "Hoofdwachtwoord verwijderen" }, "removedMasterPassword": { - "message": "Hoofdwachtwoord verwijderd." + "message": "Hoofdwachtwoord verwijderd" }, "allowSso": { "message": "SSO-authenticatie toestaan" @@ -6204,37 +6357,37 @@ "message": "Het roteren van het factureringssynchronisatietoken maakt het vorige token ongeldig." }, "selfHostedServer": { - "message": "self-hosted" + "message": "zelf gehost" }, "customEnvironment": { - "message": "Custom environment" + "message": "Aangepaste omgeving" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Geef de basis URL op van je on-premises gehoste Bitwarden installatie. Voorbeeld: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Voor geavanceerde configuratie kun je de basis URL van elke service onafhankelijk opgeven." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, "apiUrl": { - "message": "API server URL" + "message": "API-server URL" }, "webVaultUrl": { - "message": "Web vault server URL" + "message": "Webkluisserver URL" }, "identityUrl": { - "message": "Identity server URL" + "message": "Identiteitsserver URL" }, "notificationsUrl": { - "message": "Notifications server URL" + "message": "Meldingen server URL" }, "iconsUrl": { - "message": "Icons server URL" + "message": "Pictogrammen server URL" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "Omgevings-URL's opgeslagen" }, "selfHostingTitle": { "message": "Zelfgehost" @@ -6243,7 +6396,7 @@ "message": "Voor het instellen van je organisatie op je eigen server, moet je je licentiebestand uploaden. Om gratis Families-plannen en geavanceerde factureringsmogelijkheden voor je zelfgehoste organisatie te ondersteunen, moet je factureringssynchronisatie instellen." }, "billingSyncApiKeyRotated": { - "message": "Token geroteerd." + "message": "Token geroteerd" }, "billingSyncKeyDesc": { "message": "Er is een factureringssynchronisatietoken van de abonnementsinstellingen van je cloudorganisatie vereist voor het afronden van dit formulier." @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Vereist als Entiteit ID geen URL is." }, + "offerNoLongerValid": { + "message": "Deze aanbieding is niet langer geldig. Neem contact op met je organisatiebeheerders voor meer informatie." + }, "openIdOptionalCustomizations": { "message": "Optionele aanpassingen" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "E-mailadres genereren" }, - "generatorBoundariesHint": { - "message": "Waarde moet tussen $MIN$ en $MAX$ liggen", + "spinboxBoundariesHint": { + "message": "Waarde moet tussen $MIN$ en $MAX$ liggen.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Gebruik $RECOMMENDED$ tekens of meer om een sterk wachtwoord te genereren.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Gebruik $RECOMMENDED$ woorden of meer om een sterke wachtwoordzin te genereren.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Type gebruikersnaam" }, @@ -6541,7 +6717,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ fout: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -6555,11 +6731,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Gegenereerd door Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Website: $WEBSITE$. Gegenereerd door Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -6569,7 +6745,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ongeldig $SERVICENAME$ API token", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -6579,7 +6755,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ongeldige $SERVICENAME$ API token: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -6593,7 +6769,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Kan $SERVICENAME$ gemaskeerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -6603,7 +6779,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ongeldig $SERVICENAME$ domein.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -6613,7 +6789,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Ongeldige $SERVICENAME$ url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -6623,7 +6799,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Onbekende $SERVICENAME$ fout opgetreden.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -6633,7 +6809,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Onbekende doorstuurder: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -6681,6 +6857,10 @@ "message": "Automatisch in gebruikers en groepen voorzien via SCIM-provisioning van je voorkeursprovider", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatisch in gebruikers en groepen voorzien via SCIM-provisioning van je voorkeursprovider. Onderteunde integraties vinden", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM inschakelen", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7336,7 +7516,7 @@ "message": "Geef toegang tot collecties door ze aan deze groep toe te voegen." }, "restrictedCollectionAssignmentDesc": { - "message": "You can only assign collections you manage." + "message": "Je kunt alleen verzamelingen toewijzen die je beheert." }, "selectMembers": { "message": "Leden selecteren" @@ -7558,7 +7738,7 @@ "message": "Groepen selecteren" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "Rechten ingesteld voor een lid vervangen de rechten ingesteld door de groep van dat lid." }, "noMembersOrGroupsAdded": { "message": "Geen leden of groepen toegevoegd" @@ -7762,7 +7942,7 @@ "message": "Werk je versleutelingsinstellingen bij om aan de nieuwe beveiligingsaanbevelingen te voldoen en de bescherming van je account te verbeteren." }, "kdfSettingsChangeLogoutWarning": { - "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." + "message": "Als je doorgaat, log je uit van alle actieve sessies. Je zult opnieuw moeten inloggen en, indien van toepassing, tweestapsverificatie moeten voltooien. We raden aan om je kluis te exporteren voordat je je versleutelingsinstellingen wijzigt om gegevensverlies te voorkomen." }, "secretsManager": { "message": "Secrets Manager" @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Inloggen gestart" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Onthoud dit apparaat om in het vervolg naadloos in te loggen" + }, "deviceApprovalRequired": { "message": "Apparaattoestemming vereist. Kies een goedkeuringsoptie hieronder:" }, + "deviceApprovalRequiredV2": { + "message": "Toestemming apparaat vereist" + }, + "selectAnApprovalOptionBelow": { + "message": "Kies hieronder een goedkeuringsoptie" + }, "rememberThisDevice": { "message": "Dit apparaat onthouden" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Aanvraag goedkeuren" }, + "deviceApproved": { + "message": "Apparaat goedgekeurd" + }, + "deviceRemoved": { + "message": "Apparaat verwijderd" + }, + "removeDevice": { + "message": "Apparaat verwijderen" + }, + "removeDeviceConfirmation": { + "message": "Weet je zeker dat je dit apparaat wilt verwijderen?" + }, "noDeviceRequests": { "message": "Geen apparaatverzoeken" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Gebruikerse-mailadres ontbreekt" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mailadres voor actieve gebruiker niet gevonden. Je wordt uitgelogd." + }, "deviceTrusted": { "message": "Vertrouwd apparaat" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Het gedrag van de collectie beheren voor de organisatie" }, - "limitCollectionCreationDeletionDesc": { - "message": "Aanmaken en verwijderen van collecties tot eigenaren en managers beperken" - }, "limitCollectionCreationDesc": { "message": "Aanmaken van collecties beperken tot eigenaren en managers" }, @@ -8408,10 +8609,10 @@ "message": "Je hebt geen toegang om deze collectie te beheren." }, "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "message": "Ontbrekende Kan beheren machtigingen" }, "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "message": "Kan beheren machtigingen verlenen voor volledig verzamelingsbeheer, inclusief het verwijderen van verzamelingen." }, "grantCollectionAccess": { "message": "Groepen of mensen toegang tot deze collectie geven." @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "moet je een betaalmethode toevoegen", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organisatieinformatie" @@ -8509,7 +8710,7 @@ "message": "Het is niet mogelijk om jezelf toe te voegen aan groepen." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "Je kunt jezelf niet toevoegen aan verzamelingen." }, "assign": { "message": "Toewijzen" @@ -8866,65 +9067,120 @@ "sdksDesc": { "message": "Gebruik Bitwarden Secrets Manager SDK in de volgende programmeertalen om je eigen applicaties te bouwen." }, - "setUpGithubActions": { - "message": "GitHub Actions instellen" + "ssoDescStart": { + "message": "Configureren", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "voor Bitwarden met de implementatiehandleiding voor je Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Kubernetes inrichten" + "userProvisioning": { + "message": "Gebruikersvoorziening" }, - "setUpGitlabCICD": { - "message": "GitLab CI/CD instellen" + "scimIntegration": { + "message": "SCIM" }, - "setUpAnsible": { - "message": "Ansibel instellen" + "scimIntegrationDescStart": { + "message": "Configureren ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "rustSDKRepo": { - "message": "Ruby-repository bekijken" + "scimIntegrationDescEnd": { + "message": "(Systeem voor Cross-domain Identity Management) om Bitwarden automatisch van gebruikers en groepen te voorzien met behulp van de implementatiehandleiding voor je Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "C#-repository bekijken" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "cPlusPlusSDKRepo": { - "message": "C++-repository bekijken" + "bwdcDesc": { + "message": "Configureer Bitwarden Directory Connector om gebruikers en groepen automatisch te voorzien van het gebruik van de implementatiehandleiding voor jouw Identity Provider." }, - "jsWebAssemblySDKRepo": { - "message": "JS WebAssembly-repository bekijken" + "eventManagement": { + "message": "Evenementbeheer" }, - "javaSDKRepo": { - "message": "Java-repository bekijken" + "eventManagementDesc": { + "message": "Integreer Bitwarden eventlogs met je SIEM (systeeminformatie en evenementbeheer)-systeem door gebruik te maken van de implementatiehandleiding voor je platform." }, - "pythonSDKRepo": { - "message": "Python-repository bekijken" + "deviceManagement": { + "message": "Apparaatbeheer" }, - "phpSDKRepo": { - "message": "Php-repository bekijken" + "deviceManagementDesc": { + "message": "Apparaatbeheer voor Bitwarden configureren met behulp van de implementatiehandleiding voor jouw platform." }, - "rubySDKRepo": { - "message": "Ruby-repository bekijken" + "integrationCardTooltip": { + "message": "$INTEGRATION$ implementatiehandleiding openen.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "goSDKRepo": { - "message": "Go-repository bekijken" + "smIntegrationTooltip": { + "message": "$INTEGRATION$ configureren.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "$SDK$ repository weergeven", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$-implementatiehandleiding in een nieuwe tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "$SDK$-repository in een nieuwe tab weergeven.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "$INTEGRATION$-implementatiehandleiding in een nieuwe tab configureren.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Maak een nieuwe clientorganisatie aan om te beheren als Aanbieder. Extra plaatsen worden weergegeven in de volgende factureringscyclus." }, "selectAPlan": { - "message": "Select a plan" + "message": "Selecteer een plan" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% korting" }, "monthPerMember": { - "message": "month per member" + "message": "maand per lid" }, "seats": { - "message": "Seats" + "message": "Personen" }, "addOrganization": { - "message": "Add organization" + "message": "Organisatie toevoegen" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Nieuwe klant succesvol aangemaakt" }, "noAccess": { "message": "Geen toegang" @@ -8933,16 +9189,16 @@ "message": "Deze collectie is alleen toegankelijk vanaf de admin console" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Organisatiemenu togglen" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Kluisitem selecteren" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Verzamelitem selecteren" }, "manageBillingFromProviderPortalMessage": { - "message": "Manage billing from the Provider Portal" + "message": "Facturering beheren vanuit het aanbiederportaal" }, "continueSettingUpFreeTrial": { "message": "Doorgaan met het instellen van je gratis proefperiode van Bitwarden" @@ -8963,7 +9219,7 @@ "message": "Voer je organisatie-informatie voor Enterprise in" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Bekijk items in $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8973,7 +9229,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Terug naar $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8983,11 +9239,11 @@ } }, "back": { - "message": "Back", + "message": "Terug", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ verwijderen", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8997,13 +9253,13 @@ } }, "viewInfo": { - "message": "View info" + "message": "Bekijk info" }, "viewAccess": { - "message": "View access" + "message": "Toegang bekijken" }, "noCollectionsSelected": { - "message": "You have not selected any collections." + "message": "Je hebt geen verzamelingen geselecteerd." }, "updateName": { "message": "Naam bijwerken" @@ -9012,13 +9268,19 @@ "message": "Organisatienaam bijgewerkt" }, "providerPlan": { - "message": "Managed Service Provider" + "message": "Beheerde dienstaanbieder" + }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organisatie onderneming" }, "orgSeats": { - "message": "Organization Seats" + "message": "Organisatie plaatsen" }, "providerDiscount": { - "message": "$AMOUNT$% Discount", + "message": "$AMOUNT$% korting", "placeholders": { "amount": { "content": "$1", @@ -9027,7 +9289,7 @@ } }, "lowKDFIterationsBanner": { - "message": "Laag aantal KDF-iteraties. Verhoog je iteraties om de veiligheid van je account te verbeteren," + "message": "Laag aantal KDF-iteraties. Verhoog je iteraties om de veiligheid van je account te verbeteren." }, "changeKDFSettings": { "message": "KDF-instellingen wijzigen" @@ -9039,10 +9301,10 @@ "message": "Bescherm je gezin of bedrijf" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Close security gaps with monitoring reports" + "message": "Beveiligingslekken dichten met bewakingsrapporten" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + "message": "Blijf kwetsbaarheden in de beveiliging voor door te upgraden naar een betaald plan voor verbeterde monitoring." }, "approveAllRequests": { "message": "Alle verzoeken goedkeuren" @@ -9057,13 +9319,25 @@ "message": "Bitcoin" }, "updatedTaxInformation": { - "message": "Updated tax information" + "message": "Bijgewerkte belastinggegevens" + }, + "billingInvalidTaxIdError": { + "message": "Ongeldig btw-nummer, als je denkt dat dit een fout is, neem dan contact op met support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We konden je btw-nummer niet valideren, als je denkt dat dit een fout is, neem dan contact op met support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Ongeldig btw-nummer, als je denkt dat dit een fout is, neem dan contact op met support." + }, + "billingPreviewInvoiceError": { + "message": "Er is een fout opgetreden met het weergeven van de factuur. Probeer het later nog eens." }, "unverified": { - "message": "Unverified" + "message": "Niet-geverifieerd" }, "verified": { - "message": "Verified" + "message": "Geverifieerd" }, "viewSecret": { "message": "Geheim weergeven" @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB extra opslagruimte" }, + "sshKeyAlgorithm": { + "message": "Sleutelalgoritme" + }, + "sshKeyFingerprint": { + "message": "Vingerafdruk" + }, + "sshKeyPrivateKey": { + "message": "Privésleutel" + }, + "sshKeyPublicKey": { + "message": "Publieke sleutel" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9462,7 +9760,7 @@ "message": "Je Secrets Manager-abonnement zal upgraden naar het geselecteerde abonnement" }, "bitwardenPasswordManager": { - "message": "Bitwarden Password Manager" + "message": "Bitwarden Wachtwoordbeheerder" }, "secretsManagerComplimentaryPasswordManager": { "message": "Je gratis eenjarige Password Manager-abonnement zal veranderen naar het geselecteerde abonnement. Er worden pas kosten in rekening gebracht als de gratis periode voorbij is." @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Zelf hosten" }, - "verified-domain-single-org-warning": { - "message": "Het verifiëren van een domein zal het enkelvoudig organisatiebeleid inschakelen." + "claim-domain-single-org-warning": { + "message": "Het verifiëren van een domein zal enkelvoudig organisatiebeleid inschakelen." }, "single-org-revoked-user-warning": { "message": "Niet-conforme leden worden ingetrokken. Beheerders kunnen leden herstellen zodra ze alle andere organisaties verlaten." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Als je een lid verwijdert, verwijder je permanent hun Bitwarden-account en individuele kluisgegevens. Collectiegegevens blijven in de organisatie. Om het account te heractiveren, moet het lid een account aanmaken en opnieuw on-boarding doorlopen.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Dit verwijdert alle items in eigendom van $NAME$ permanent. Items in een collectie blijven buiten schot.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Dit verwijdert alle items in eigendom van de volgende leden permanent. Items in een collectie blijven buiten schot.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "$NAME$ verwijderd", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "De gebruiker is verwijderd uit de organisatie en alle bijbehorende gebruikersgegevens zijn verwijderd." + }, + "deletedUserId": { + "message": "Gebruiker $ID$ verwijderd - een eigenaar / beheerder heeft het gebruikersaccount verwijderd", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Gebruiker $ID$ heeft organisatie verlaten", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "De $ORGANIZATION$ is geschorst", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Neem contact op met de eigenaar van je organisatie voor hulp." + }, + "suspendedOwnerOrgMessage": { + "message": "Voeg een betaalmethode toe om toegang tot je organisatie te krijgen." + }, + "deleteMembers": { + "message": "Leden verwijderen" + }, + "noSelectedMembersApplicable": { + "message": "Deze actie is niet van toepassing op de geselecteerde leden." + }, + "deletedSuccessfully": { + "message": "Succesvol verwijderd" + }, + "freeFamiliesSponsorship": { + "message": "Gratis Bitwarden Families-sponsoring verwijderen" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Sta leden niet toe om een familieplan te verzilveren via deze organisatie." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Betaling met een bankrekening is alleen beschikbaar voor klanten in de Verenigde Staten. Je moet je bankrekening verifiëren. We zullen binnen 1-2 werkdagen een microbetaling uitvoeren. Voer de code van het bankafschrift uit deze storting in op de factuurpagina van de organisatie om de bankrekening te verifiëren. Als de bankrekening niet wordt geverifieerd, wordt er een betaling gemist en wordt je abonnement opgeschort." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We hebben een microbetaling op je bankrekening gedaan (dit kan 1-2 werkdagen duren). Voer de zescijferige code van je bankafschrift in die begint met 'SM'. Als de bankrekening niet wordt geverifieerd, wordt er een betaling gemist en wordt je abonnement opgeschort." + }, + "descriptorCode": { + "message": "Code bankafschrift" + }, + "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" + }, + "removeMembers": { + "message": "Leden verwijderen" + }, + "devices": { + "message": "Apparaten" + }, + "deviceListDescription": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld. Als je een apparaat niet herkent, verwijder het nu." + }, + "deviceListDescriptionTemp": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld." + }, + "claimedDomains": { + "message": "Geverifieerde domeinen" + }, + "claimDomain": { + "message": "Domein verifiëren" + }, + "reclaimDomain": { + "message": "Domein opnieuw verifiëren" + }, + "claimDomainNameInputHint": { + "message": "Voorbeeld: mijndomein.com. Subdomeinen moet je afzonderlijk verifiëren." + }, + "automaticClaimedDomains": { + "message": "Automatisch geverifieerde domeinen" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden probeert het domein gedurende de eerste 72 uur driemaal te verifiëren. Als het domein niet geverifieerd kan worden, controleer dan het DNS-record bij je host en verifieer handmatig. Het domein wordt binnen 7 dagen verwijderd uit je organisatie als het niet geverifieerd is." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ niet geverifieerd. Controleer je DNS-records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Geverifieerd" + }, + "domainStatusUnderVerification": { + "message": "Gebruikersverificatie" + }, + "claimedDomainsDesc": { + "message": "Verifieer een domein om eigenaar te worden van alle leden wiens e-mailadres overeenkomt met het domein. Leden kunnen de SSO-identificatie overslaan wanneer ze inloggen. Beheerders kunnen accounts van lelden verwijderen." + }, + "invalidDomainNameClaimMessage": { + "message": "Invoer is geen geldig formaat. Formaat: mijndomein.com. Subdomeinen moet je afzonderlijk verifiëren." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ geverifieerd", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ niet geverifieerd", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Als je $EMAIL$ verwijdert, kun je de sponsoring voor dit Familie-plan niet gebruiken. Weet je zeker dat je door wilt gaan?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Als je $EMAIL$ verwijdert, eindigt de sponsoring voor dit Familie-plan en brengen we $DATE$ $40 + belasting in rekening. Je kunt geen nieuwe sponsoring inwisselen tot $DATE$. Weet je zeker dat u wilt doorgaan?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domein geverifieerd" + }, + "organizationNameMaxLength": { + "message": "Organisatienaam mag niet langer zijn dan 50 tekens." + }, + "resellerRenewalWarning": { + "message": "Je abonnement wordt binnenkort verlengd. Neem voor $RENEWAL_DATE$ contact op met $RESELLER$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Er is een factuur voor je abonnement aangemaakt op $ISSUED_DATE$. Neem contact op met $RESELLER$ voor $DUE_DATE$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "De factuur voor je abonnement is niet betaald. Neem contact op met $RESELLER$ voor $GRACE_PERIOD_END$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organisatieabonnement hervat" + }, + "restartSubscription": { + "message": "Abonnement hervatten" + }, + "suspendedManagedOrgMessage": { + "message": "Neem contact op met $PROVIDER$ voor hulp.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 7bbd7e75bf5..cb6d070485d 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Trygg notat" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Innskrivingar" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Passordoversyn" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Det er ingen passord å syna." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Tøm", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Eining" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 57cc9a404e1..e6cce3405d5 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 13b870d4706..d063fa818a7 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1,18 +1,21 @@ { "allApplications": { - "message": "All applications" + "message": "Wszystkie aplikacje" }, "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -24,7 +27,16 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Powiadomieni członkowie" + }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", @@ -84,7 +96,7 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "Aplikacja" }, "atRiskPasswords": { "message": "At-risk passwords" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Bezpieczna notatka" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Dane logowania" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Logowanie za pomocą urządzenia musi być włączone w ustawieniach aplikacji Bitwarden. Potrzebujesz innej opcji?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Logowanie hasłem głównym" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Zweryfikuj swoją tożsamość" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Logowanie rozpoczęte" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Powiadomienie zostało wysłane na urządzenie." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Wersja $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Historia hasła" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Brak haseł." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Wyczyść", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Zaloguj się ponownie." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Zaloguj się ponownie. Jeśli używasz innych aplikacji Bitwarden, wyloguj się i zaloguj ponownie również w nich." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Wszystkie sesje zostały zakończone" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ten użytkownik korzysta z logowania dwustopniowego, aby chronić swoje konto." }, @@ -3333,7 +3396,7 @@ "message": "Niepoprawny PIN" }, "pin": { - "message": "PIN", + "message": "Kod PIN", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Zobacz wszystkie sposoby logowania" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Urządzenie" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Tworzenie konta na" }, @@ -3831,14 +3906,66 @@ "updateBrowser": { "message": "Aktualizuj przeglądarkę" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Używasz nieobsługiwanej przeglądarki. Sejf internetowy może działać niewłaściwie." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Dołącz do organizacji" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Dołącz do $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -4388,6 +4515,9 @@ "message": "Nie pytaj ponownie o weryfikację unikalnego identyfikatora konta dla zaproszonych użytkowników (niezalecane)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Darmowy", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Zaloguj się za pomocą logowania jednokrotnego SSO swojej organizacji. Aby rozpocząć, wpisz swój identyfikator organizacji." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Logowanie jednokrotne" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Wykluczono, nie dotyczy tej akcji." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Unikalny identyfikator konta" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Zarządzanie użytkownikami musi być również przyznane z uprawnieniem do zarządzania odzyskiwaniem kont" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Wiadomość została wysłana" }, - "revokeSponsorshipConfirmation": { - "message": "Po usunięciu konta, właściciel organizacji rodzinnej będzie odpowiedzialny za subskrypcję i powiązane faktury. Czy na pewno chcesz kontynuować?" - }, "removeSponsorshipSuccess": { "message": "Sponsoring został usunięty" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Wymagane, jeśli identyfikatorem podmiotu nie jest adres URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Opcjonalne dostosowania" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Rodzaj nazwy użytkownika" }, @@ -6681,6 +6857,10 @@ "message": "Automatycznie aprowiduj użytkowników i grupy z preferowanym dostawcą tożsamości poprzez aprowizację SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Włącz SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -6800,10 +6980,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 pole wymaga Twojej uwagi." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ pól wymaga twojej uwagi.", "placeholders": { "count": { "content": "$1", @@ -7672,13 +7852,13 @@ "message": "lub" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Odblokuj za pomocą danych biometrycznych" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Odblokuj kodem PIN" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Odblokuj przy użyciu hasła głównego" }, "licenseAndBillingManagement": { "message": "Zarządzanie licencjami i rozliczeniami" @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Logowanie rozpoczęte" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Wymagane zatwierdzenie urządzenia. Wybierz opcję zatwierdzenia poniżej:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Zapamiętaj to urządzenie" }, @@ -8019,7 +8208,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "z $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Zatwierdź prośbę" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Brak urządzeń do zatwierdzenia" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Brak adresu e-mail użytkownika" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Zaufano urządzeniu" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Zarządzaj zachowaniami kolekcji w organizacji" }, - "limitCollectionCreationDeletionDesc": { - "message": "Ogranicz tworzenie i usuwanie kolekcji dla właścicieli i administratorów" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "dodaj metodę płatności", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informacje o organizacji" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Użyj SDK Menedżera Sekretów Bitwarden w następujących językach programowania, aby zbudować własne aplikacje." }, - "setUpGithubActions": { - "message": "Skonfiguruj Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Skonfiguruj Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Skonfiguruj GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Skonfiguruj Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "Zobacz repozytorium Rust" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "Zobacz repozytorium C#" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "Zobacz repozytorium C++" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "Zobacz repozytorium JS WebAssembly" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "Zobacz repozytorium Java" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "Zobacz repozytorium Pythona" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "Zobacz repozytorium php" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "Zobacz repozytorium Ruby" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "Zobacz repozytorium Go" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Utwórz nową organizację klienta do zarządzania jako dostawca. Dodatkowe miejsca zostaną odzwierciedlone w następnym cyklu rozliczeniowym." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Dostawca usług zarządzanych" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Miejsca w organizacji" }, @@ -9027,7 +9289,7 @@ } }, "lowKDFIterationsBanner": { - "message": "Niska liczba itracjia KDF. Zwiększ liczbę iteracji, aby zwiększyć bezpieczeństwo Twojego konta." + "message": "Niska liczba iteracji KDF. Zwiększ liczbę iteracji, aby zwiększyć bezpieczeństwo Twojego konta." }, "changeKDFSettings": { "message": "Zmień ustawienia KDF" @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Zaktualizowane informacje podatkowe" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Niezweryfikowane" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB dodatkowej przestrzeni" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 kont premium" }, @@ -9507,7 +9805,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Dołącz małe litery", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9515,7 +9813,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Dołącz numery", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9523,7 +9821,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Dołącz znaki specjalne", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -9531,13 +9829,13 @@ "description": "Label for the password generator special characters checkbox" }, "addAttachment": { - "message": "Add attachment" + "message": "Dodaj załącznik" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Maksymalny rozmiar pliku to 500 MB" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Czy na pewno chcesz trwale usunąć ten załącznik?" }, "manageSubscriptionFromThe": { "message": "Manage subscription from the", @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 5984d3472e4..b911d5b273a 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1,21 +1,24 @@ { "allApplications": { - "message": "Todos os aplicativos" + "message": "Todas as aplicações" }, "criticalApplications": { - "message": "Critical applications" + "message": "Aplicações críticas" + }, + "accessIntelligence": { + "message": "Acessar a Inteligência" }, "riskInsights": { - "message": "Risk Insights" + "message": "Intuições de risco" }, "passwordRisk": { - "message": "Password Risk" + "message": "Risco de senha" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Revise as senhas de risco (fracas, expostas ou reutilizadas) em todos os aplicativos. Selecione suas aplicações mais críticas para priorizar ações de segurança para seus usuários resolverem senhas de risco." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Dados atualizados pela última vez: $DATE$", "placeholders": { "date": { "content": "$1", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Membros notificados" }, + "revokeMembers": { + "message": "Remover membro" + }, + "restoreMembers": { + "message": "Restaurar membro" + }, + "cannotRestoreAccessError": { + "message": "Não é possível restaurar acesso à organização" + }, "allApplicationsWithCount": { "message": "Todas as aplicações ($COUNT$)", "placeholders": { @@ -36,10 +48,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Criar item de \"login\"" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Aplicações críticas ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -57,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Nenhum aplicativo encontrado em $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -66,22 +78,22 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Como os usuários salvam as credenciais, os aplicativos aparecem aqui, mostrando qualquer senha de risco. Marque aplicativos críticos e notifique os usuários a atualizar senhas." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Você não marcou nenhum aplicativo como Crítico" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "Selecione suas aplicações mais críticas para descobrir senhas de risco e notifique os usuários a alterar essas senhas." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "Marcar aplicativos críticos" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "Marcar aplicativo como crítico" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "Aplicativos marcados como críticos" }, "application": { "message": "Aplicativo" @@ -90,13 +102,13 @@ "message": "Senhas de risco" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Solicitar alteração de senha" }, "totalPasswords": { "message": "Total de senhas" }, "searchApps": { - "message": "Search applications" + "message": "Procurar aplicativos" }, "atRiskMembers": { "message": "Membros de risco" @@ -160,19 +172,19 @@ "message": "Credenciais de login" }, "personalDetails": { - "message": "Personal details" + "message": "Detalhes pessoais" }, "identification": { - "message": "Identification" + "message": "Identificação" }, "contactInfo": { - "message": "Contact info" + "message": "Informação de contato" }, "cardDetails": { - "message": "Card details" + "message": "Detalhes do cartão" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Detalhes $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -181,7 +193,7 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Histórico do item" }, "authenticatorKey": { "message": "Chave do autenticador" @@ -255,7 +267,7 @@ "message": "Código de Segurança (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "Código de segurança / CVV" }, "identityName": { "message": "Nome de Identidade" @@ -381,17 +393,17 @@ "message": "Booleano" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Caixa de seleção" }, "cfTypeLinked": { "message": "Vinculado", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "Tipo do campo" }, "fieldLabel": { - "message": "Field label" + "message": "Rótulo do campo" }, "remove": { "message": "Remover" @@ -457,7 +469,7 @@ "message": "Gerar Senha" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Gerar frase secreta" }, "checkPassword": { "message": "Verifique se a senha foi exposta." @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Nota Segura" }, + "typeSshKey": { + "message": "Chave SSH" + }, "typeLoginPlural": { "message": "Logins" }, @@ -590,7 +605,7 @@ "message": "Nome Completo" }, "address": { - "message": "Address" + "message": "Endereço" }, "address1": { "message": "Endereço 1" @@ -635,7 +650,7 @@ "message": "Visualizar Item" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Nova $TYPE$", "placeholders": { "type": { "content": "$1", @@ -644,7 +659,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Editar $TYPE$", "placeholders": { "type": { "content": "$1", @@ -707,7 +722,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Copiado com sucesso" }, "copyValue": { "message": "Copiar Valor", @@ -718,7 +733,7 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copiar senha", "description": "Copy passphrase to clipboard" }, "passwordCopied": { @@ -741,7 +756,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Copiar $FIELD$", "placeholders": { "field": { "content": "$1", @@ -750,34 +765,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Copiar site" }, "copyNotes": { - "message": "Copy notes" + "message": "Copiar Notas" }, "copyAddress": { - "message": "Copy address" + "message": "Copiar endereço" }, "copyPhone": { - "message": "Copy phone" + "message": "Copiar telefone" }, "copyEmail": { - "message": "Copy email" + "message": "Copiar e-mail" }, "copyCompany": { - "message": "Copy company" + "message": "Copiar empresa" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Cadastro de Pessoas Físicas" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copiar número do passaporte" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copiar número da CNH" }, "copyName": { - "message": "Copy name" + "message": "Copiar nome" }, "me": { "message": "Eu" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Login com dispositivo deve ser habilitado nas configurações do aplicativo móvel do Bitwarden. Necessita de outra opção?" }, + "needAnotherOptionV1": { + "message": "Precisa de outra opção?" + }, "loginWithMasterPassword": { "message": "Entrar com senha mestra" }, @@ -991,13 +1009,13 @@ "message": "Use um método de login diferente" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Iniciar sessão com a chave de acesso" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usar login único" }, "welcomeBack": { - "message": "Welcome back" + "message": "Bem vindo de volta" }, "invalidPasskeyPleaseTryAgain": { "message": "Senha inválida. Por favor, tente novamente." @@ -1081,7 +1099,7 @@ "message": "Criar conta" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novo no Bitwarden?" }, "setAStrongPassword": { "message": "Defina uma senha forte" @@ -1099,11 +1117,23 @@ "message": "Iniciar sessão" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Inicie a sessão no Bitwarden" + }, + "authenticationTimeout": { + "message": "Tempo de autenticação esgotado" + }, + "authenticationSessionTimedOut": { + "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de “login”." }, "verifyIdentity": { "message": "Verifique sua identidade" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Login iniciado" }, @@ -1160,16 +1190,16 @@ "message": "Configurações" }, "accountEmail": { - "message": "Account email" + "message": "Email da conta" }, "requestHint": { - "message": "Request hint" + "message": "Pedir dica" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Dica da senha mestra" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" }, "passwordHint": { "message": "Dica da senha" @@ -1209,10 +1239,10 @@ "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Sua nova conta foi criada!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Você está conectado!" }, "trialAccountCreated": { "message": "Conta criada com sucesso." @@ -1233,7 +1263,7 @@ "message": "Seu cofre está bloqueado" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Sua conta está bloqueada" }, "uuid": { "message": "UUID" @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Uma notificação foi enviada para seu dispositivo." + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Certifique-se que sua conta esteja desbloqueado e que a frase de impressão digital corresponda à do outro dispositivo." + }, "versionNumber": { "message": "Versão $VERSION_NUMBER$", "placeholders": { @@ -1621,15 +1657,33 @@ "message": "Incluir Número" }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Os requisitos de política empresarial foram aplicados às suas opções de gerador.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { "message": "Histórico de Senha" }, + "generatorHistory": { + "message": "Histórico do gerador" + }, + "clearGeneratorHistoryTitle": { + "message": "Limpar histórico do gerador" + }, + "cleargGeneratorHistoryDescription": { + "message": "Se continuar, todas as entradas serão permanentemente excluídas do histórico do gerador. Tem certeza que deseja continuar?" + }, "noPasswordsInList": { "message": "Não existem senhas para listar." }, + "clearHistory": { + "message": "Limpar histórico" + }, + "nothingToShow": { + "message": "Nada a mostrar" + }, + "nothingGeneratedRecently": { + "message": "Você não gerou nada recentemente" + }, "clear": { "message": "Limpar", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Por favor, reinicie a sessão." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, reinicie a sessão. Se estiver usando outros aplicativos do Bitwarden, encerre a sessão e reinicie também." }, @@ -1738,7 +1798,7 @@ "message": "Cuidado, essas ações não são reversíveis!" }, "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" + "message": "Cuidado, esta ação não é reversível!" }, "deauthorizeSessions": { "message": "Desautorizar sessões" @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Todas as Sessões Desautorizadas" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Esta conta pertence a $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -2357,7 +2417,7 @@ "message": "Verificar Senhas Expostas" }, "timesExposed": { - "message": "Times exposed" + "message": "Vezes expostas" }, "exposedXTimes": { "message": "Exposta $COUNT$ vez(es)", @@ -2394,7 +2454,7 @@ "message": "Nenhum item no seu cofre tem senhas fracas." }, "weakness": { - "message": "Weakness" + "message": "Fraqueza" }, "reusedPasswordsReport": { "message": "Relatório de Senhas Reutilizadas" @@ -2422,7 +2482,7 @@ "message": "Nenhuma credencial no seu cofre tem senhas que estão sendo reutilizadas." }, "timesReused": { - "message": "Times reused" + "message": "Vezes reutilizadas" }, "reusedXTimes": { "message": "Reutilizada $COUNT$ vez(es)", @@ -2722,7 +2782,7 @@ "message": "Baixar Licença" }, "viewBillingToken": { - "message": "View Billing Token" + "message": "Ver Token de Cobrança" }, "updateLicense": { "message": "Atualizar Licença" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Este usuário está usando o login em duas etapas para proteger a sua conta." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Ver todas as opções de “login”" + }, "viewAllLoginOptions": { "message": "Ver todas as opções de login" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Criando conta em" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Atualizar Navegador" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Você está usando um navegador da Web não suportado. O cofre web pode não funcionar corretamente." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Junte-se a Organização" }, @@ -4168,10 +4295,10 @@ } }, "encryptionKeyUpdateCannotProceed": { - "message": "Encryption key update cannot proceed" + "message": "A atualização da chave de criptografia não pode continuar" }, "keyUpdateFoldersFailed": { - "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." + "message": "Ao atualizar sua chave de criptografia, suas pastas não puderam ser descriptografadas. Para continuar com a atualização, suas pastas devem ser excluídas. Nenhum item de cofre será excluído se você prosseguir." }, "keyUpdated": { "message": "Chave Atualizada" @@ -4249,7 +4376,7 @@ "message": "Tempo Limite do Cofre" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Tempo esgotado" }, "vaultTimeoutDesc": { "message": "Escolha quando o tempo limite do seu cofre irá se esgotar e execute a ação selecionada." @@ -4318,7 +4445,7 @@ "message": "Selecionado" }, "recommended": { - "message": "Recommended" + "message": "Recomendado" }, "ownership": { "message": "Propriedade" @@ -4388,6 +4515,9 @@ "message": "Não peça para verificar a frase biométrica novamente", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Você será notificado assim que o pedido for aprovado" + }, "free": { "message": "Gratuito", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Faça o login usando o portal de login único da sua organização. Por favor, insira o identificador da sua organização para começar." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Iniciar Sessão Empresarial Única" }, @@ -4686,7 +4822,7 @@ "message": "Restringir os usuários de poderem entrar em outras organizações." }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "Restringir membros de ingressarem em outras organizações. Essa política é necessária para organizações que tenham habilitado a verificação de domínio." }, "singleOrgBlockCreateMessage": { "message": "Sua organização atual tem uma política que não permite que você entre em mais de uma organização. Por favor, entre em contato com os administradores da sua organização ou cadastre-se a partir de uma conta do Bitwarden diferente." @@ -4695,7 +4831,7 @@ "message": "Os membros da organização que não são Donos ou Administradores, e já são membros de outra organização serão removidos da sua organização." }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "Membros não conformes serão colocados no estado revogado até que saiam de todas as outras organizações. Administradores estão isentos e podem restaurar os membros assim que cumprir." }, "requireSso": { "message": "Autenticação de Acesso Único" @@ -5302,7 +5438,7 @@ } }, "viewSend": { - "message": "View Send", + "message": "Ver Enviar", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluído, não aplicável para esta ação." }, + "nonCompliantMembersTitle": { + "message": "Membros não compatíveis" + }, + "nonCompliantMembersError": { + "message": "Os membros que não estão em conformidade com a política de “login” único ou de duas etapas não poderão ser restaurados até cumprirem os requisitos da política" + }, "fingerprint": { "message": "Impressão digital" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gerenciar usuários também devem ser concedidos com a permissão de gerenciar a recuperação de contas" }, @@ -5730,7 +5886,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-mail enviado" }, - "revokeSponsorshipConfirmation": { - "message": "Depois de remover esta conta, o proprietário da organização das Famílias será responsável por essa assinatura e faturas relacionadas. Tem certeza de que quer continuar?" - }, "removeSponsorshipSuccess": { "message": "Patrocínio Removido" }, @@ -6177,7 +6330,7 @@ "message": "Ver Token de Faturamento Sync" }, "generateBillingToken": { - "message": "Generate billing token" + "message": "Gerar token de faturamento" }, "copyPasteBillingSync": { "message": "Copie e cole esse token nas configurações de Faturamento Sync da sua organização auto-hospedada." @@ -6186,7 +6339,7 @@ "message": "Seu token de faturamento Sync pode acessar e editar as configurações de assinatura desta organização." }, "manageBillingTokenSync": { - "message": "Manage Billing Token" + "message": "Gerenciar token de faturamento" }, "setUpBillingSync": { "message": "Configurar sincronização de faturamento" @@ -6252,7 +6405,7 @@ "message": "Token de Sincronização de Faturamento" }, "automaticBillingSyncDesc": { - "message": "Automatic sync unlocks Families sponsorships and allows you to sync your license without uploading a file. After making updates in the Bitwarden cloud server, select Sync License to apply changes." + "message": "Sincronização automática desbloqueia os patrocínios das famílias e permite-lhe sincronizar sua licença sem enviar um arquivo. Depois de fazer atualizações no servidor da nuvem do Bitwarden, selecione Sincronizar licença para aplicar as alterações." }, "active": { "message": "Ativo" @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Necessário se o ID da Entidade não for um URL." }, + "offerNoLongerValid": { + "message": "Esta oferta não é mais válida. Entre em contato com os administradores da sua organização para obter mais informações." + }, "openIdOptionalCustomizations": { "message": "Personalizações opcionais" }, @@ -6411,10 +6567,10 @@ "message": "Gerar Usuário" }, "generateEmail": { - "message": "Generate email" + "message": "Gerar e-mail" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Valor deve ser entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ caracteres ou mais para gerar uma senha forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use palavras $RECOMMENDED$ ou mais para gerar uma frase secreta 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo de Usuário" }, @@ -6533,11 +6709,11 @@ "message": "Gere um alias de e-mail com um serviço externo de encaminhamento." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domínio de e-mail", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Escolha um domínio que seja suportado pelo serviço selecionado", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -6681,6 +6857,10 @@ "message": "Provisionar automaticamente usuários e grupos com seu provedor de identidade preferido via provisionamento de SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Provisionar automaticamente usuários e grupos com seu provedor de identidade preferido via provisionamento de SCIM. Encontre suporte para as integrações", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Habilitar SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -6800,10 +6980,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 campo precisa de sua atenção." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "Campos $COUNT$ precisam de sua atenção.", "placeholders": { "count": { "content": "$1", @@ -7660,7 +7840,7 @@ "message": "Enviar de arquivo" }, "upload": { - "message": "Upload" + "message": "Fazer upload" }, "acceptedFormats": { "message": "Formatos Aceitos:" @@ -7672,13 +7852,13 @@ "message": "ou" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloquear com a biometria" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Desbloquear com o PIN" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloquear com a senha mestra" }, "licenseAndBillingManagement": { "message": "Gerenciamento da licença e faturamento" @@ -7690,7 +7870,7 @@ "message": "Upload manual" }, "manualBillingTokenUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships." + "message": "Se você não deseja optar pela sincronização de faturamento, carregue sua licença manualmente aqui. Isto não desbloqueará automaticamente os patrocinadores das Famílias." }, "syncLicense": { "message": "Sincronizar licença" @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Sessão iniciada" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Lembre-se deste dispositivo para permanecer conectado" + }, "deviceApprovalRequired": { "message": "Aprovação do dispositivo necessária. Selecione uma opção de aprovação abaixo:" }, + "deviceApprovalRequiredV2": { + "message": "Aprovação do dispositivo necessária" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecione uma opção de aprovação abaixo" + }, "rememberThisDevice": { "message": "Lembrar deste dispositivo" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Aprovar solicitação" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nenhum pedido de dispositivo" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "E-mail do usuário ausente" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Endereço eletrônico de usuário ativo não encontrado. Você será desconectado" + }, "deviceTrusted": { "message": "Dispositivo confiável" }, @@ -8285,14 +8489,11 @@ "collectionManagementDesc": { "message": "Gerenciar o comportamento da coleção para a organização" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limitar criação de coleção e exclusão a proprietários e administradores" - }, "limitCollectionCreationDesc": { "message": "Limitar criação de coleção e exclusão a proprietários e administradores" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "Limitar exclusão de coleção a proprietários e administradores" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Proprietários e administradores podem gerenciar todas as coleções e itens" @@ -8340,7 +8541,7 @@ "message": "URL do Servidor" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL do servidor auto-host", "description": "Label for field requesting a self-hosted integration service URL" }, "aliasDomain": { @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "adicione um método de pagamento", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informação da Organização" @@ -8552,7 +8753,7 @@ "message": "Adicionar campo" }, "editField": { - "message": "Edit field" + "message": "Editar campo" }, "items": { "message": "Itens" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Utilize o Bitwarden Secrets Manager SDK nas seguintes linguagens de programação para construir seus próprios aplicativos." }, - "setUpGithubActions": { - "message": "Configurar ações do Github" + "ssoDescStart": { + "message": "Configurar", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "Para o Bitwarden usar o guia de implementação Para o seu provedor de identidade.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Provisionamento de usuário" }, - "setUpKubernetes": { - "message": "Configurar Kubernetes" + "scimIntegration": { + "message": "SCIM" }, - "setUpGitlabCICD": { - "message": "Configurar GitLab CI/CD" + "scimIntegrationDescStart": { + "message": "Configurar", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Configurar Ansible" + "scimIntegrationDescEnd": { + "message": "(Sistema de Gerenciamento de Identidade de Domínio) para fornecer automaticamente usuários e grupos ao Bitwarden usando o guia de implementação do seu Provedor de Identidade.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "rustSDKRepo": { - "message": "Visualizar repositório Rust" + "bwdc": { + "message": "Conector de Diretório Bitwarden" }, - "cSharpSDKRepo": { - "message": "Visualizar repositório C#" + "bwdcDesc": { + "message": "Configure o conector de diretório do Bitwarden para fornecer automaticamente usuários e grupos usando o guia de implementação para o seu provedor de identidade." }, - "cPlusPlusSDKRepo": { - "message": "Visualizar repositório C++" + "eventManagement": { + "message": "Gerenciador de eventos" }, - "jsWebAssemblySDKRepo": { - "message": "Visualizar repositório WebAssembly JS" + "eventManagementDesc": { + "message": "Integre os logs de eventos do Bitwarden ao seu sistema SIEM (informações do sistema e gerenciamento de eventos) utilizando o guia de implementação da sua plataforma." + }, + "deviceManagement": { + "message": "Gerenciamento do dispositivo" + }, + "deviceManagementDesc": { + "message": "Configure o gerenciamento de dispositivos para o Bitwarden usando o guia de implementação da sua plataforma." + }, + "integrationCardTooltip": { + "message": "Inicie o guia de implementação $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "javaSDKRepo": { - "message": "Ver repositório Java" + "smIntegrationTooltip": { + "message": "Configure $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Ver repositório Python" + "smSdkTooltip": { + "message": "Visualizar repositório Rust", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Ver repositório PHP" + "integrationCardAriaLabel": { + "message": "Abrir guia de implementação $INTEGRATION$ em uma nova aba.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Ver repositório Ruby" + "smSdkAriaLabel": { + "message": "Veja o repositório $SDK$ em uma nova guia.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Visualizar repositório Go" + "smIntegrationCardAriaLabel": { + "message": "Configurar guia de implementação $INTEGRATION$ em uma nova aba.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Crie uma nova organização de cliente para gerenciar como um Provedor. Posições adicionais serão refletidas no próximo ciclo de faturamento." @@ -8945,13 +9201,13 @@ "message": "Gerenciar faturamento a partir do Portal do Provedor" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "Continue a configurar a sua avaliação gratuita do Bitwarden" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "Continue a configurar sua avaliação gratuita do Gerenciador de Senhas do Bitwarden" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "Continue configurando seu teste gratuito do Gerenciador de Segredos do Bitwarden" }, "enterTeamsOrgInfo": { "message": "Insira as informações da organização de suas equipes" @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Provedor de Serviços Gerenciados" }, + "managedServiceProvider": { + "message": "Provedor de serviço gerenciado" + }, + "multiOrganizationEnterprise": { + "message": "multi-empresa organizacional" + }, "orgSeats": { "message": "Lugares da Organização" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Informações fiscais atualizadas" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Não verificado" }, @@ -9097,25 +9371,25 @@ "message": "Avalie o acesso de membros da organização entre grupos, coleções e itens de coleções. O exporte CSV fornece informações detalhadas por membro, incluindo informações sobre permissões de coleção e configurações de conta." }, "memberAccessReportNoCollection": { - "message": "(No Collection)" + "message": "(Sem coleção)" }, "memberAccessReportNoCollectionPermission": { - "message": "(No Collection Permission)" + "message": "(Nenhuma permissão de coleção)" }, "memberAccessReportNoGroup": { - "message": "(No Group)" + "message": "(Sem Grupo)" }, "memberAccessReportTwoFactorEnabledTrue": { - "message": "On" + "message": "Ligado" }, "memberAccessReportTwoFactorEnabledFalse": { - "message": "Off" + "message": "Desligado" }, "memberAccessReportAuthenticationEnabledTrue": { - "message": "On" + "message": "Ligado" }, "memberAccessReportAuthenticationEnabledFalse": { - "message": "Off" + "message": "Desligado" }, "higherKDFIterations": { "message": "Iterações KDF mais altas podem ajudar a proteger sua senha mestra de ser descoberta por força bruta por alguém mal-intencionado." @@ -9264,64 +9538,64 @@ "message": "assentos comprados removidos" }, "environmentVariables": { - "message": "Environment variables" + "message": "Variáveis de ambiente" }, "organizationId": { - "message": "Organization ID" + "message": "ID da Organização" }, "projectIds": { - "message": "Project IDs" + "message": "IDs do projeto" }, "projectId": { - "message": "Project ID" + "message": "ID do Projeto" }, "projectsAccessedByMachineAccount": { - "message": "The following projects can be accessed by this machine account." + "message": "Os seguintes projetos podem ser acessados por esta conta de máquina." }, "config": { - "message": "Config" + "message": "Configuraçao" }, "learnMoreAboutEmergencyAccess": { - "message": "Learn more about emergency access" + "message": "Saiba mais sobre acesso de emergência" }, "learnMoreAboutMatchDetection": { - "message": "Learn more about match detection" + "message": "Saiba mais sobre a detecção de partidas" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Learn more about master password re-prompt" + "message": "Saiba mais sobre a redefinição da senha mestra" }, "learnMoreAboutSearchingYourVault": { - "message": "Learn more about searching your vault" + "message": "Saiba mais sobre como pesquisar no seu cofre" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Learn about your account fingerprint phrase" + "message": "Saiba mais sobre a sua frase biométrica" }, "impactOfRotatingYourEncryptionKey": { - "message": "Impact of rotating your encryption key" + "message": "Impacto ao girar sua chave de criptografia" }, "learnMoreAboutEncryptionAlgorithms": { - "message": "Learn more about encryption algorithms" + "message": "Saiba mais sobre os algoritmos de criptografia" }, "learnMoreAboutKDFIterations": { - "message": "Learn more about KDF iterations" + "message": "Aprenda mais sobre iterações KDF" }, "learnMoreAboutLocalization": { - "message": "Learn more about localization" + "message": "Saiba mais sobre a localização" }, "learnMoreAboutWebsiteIcons": { - "message": "Learn more about using website icons" + "message": "Saiba mais sobre como usar os ícones dos sites" }, "learnMoreAboutUserAccess": { - "message": "Learn more about user access" + "message": "Saiba mais sobre o acesso aos usuários" }, "learnMoreAboutMemberRoles": { - "message": "Learn more about member roles and permissions" + "message": "Saiba mais sobre os papéis e permissões dos membros" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "O que é um número CVVV?" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "Saiba mais sobre a API do Bitwarden" }, "fileSends": { "message": "Arquivos enviados" @@ -9381,7 +9655,7 @@ } }, "upgradePlans": { - "message": "Upgrade your plan to invite members and experience powerful security features." + "message": "Atualize o seu plano para convidar membros e experimentar poderosos recursos de segurança." }, "upgradeDiscount": { "message": "Economize $AMOUNT$%", @@ -9393,10 +9667,10 @@ } }, "enterprisePlanUpgradeMessage": { - "message": "Advanced capabilities for larger organizations" + "message": "Recursos avançados para organizações maiores" }, "teamsPlanUpgradeMessage": { - "message": "Resilient protection for growing teams" + "message": "Proteção residencial para o cultivo das equipes" }, "teamsInviteMessage": { "message": "Convide membros ilimitados" @@ -9408,7 +9682,7 @@ "message": "Sincronizar grupos e usuários de um diretório" }, "familyPlanUpgradeMessage": { - "message": "Secure your family logins" + "message": "Proteja seus logins familiares" }, "accessToPremiumFeatures": { "message": "Acesso às funcionalidades Premium" @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB de armazenamento adicional" }, + "sshKeyAlgorithm": { + "message": "Algoritmo da chave" + }, + "sshKeyFingerprint": { + "message": "Impressão digital" + }, + "sshKeyPrivateKey": { + "message": "Chave privada" + }, + "sshKeyPublicKey": { + "message": "Chave pública" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 contas premium" }, @@ -9459,29 +9757,29 @@ "message": "Atual" }, "secretsManagerSubscriptionInfo": { - "message": "Your Secrets Manager subscription will upgrade based on the plan selected" + "message": "Sua assinatura do Gerenciador de Segredos será atualizada com base no plano selecionado" }, "bitwardenPasswordManager": { "message": "Gerenciador de Senhas Bitwarden" }, "secretsManagerComplimentaryPasswordManager": { - "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over." + "message": "Sua cortesia de um ano de assinatura do Gerenciador de Senhas vai atualizar para o plano selecionado. Você não será cobrado até que o período complementar acabe." }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "Arquivo salvo no dispositivo. Gerencie a partir das transferências do seu dispositivo." }, "publicApi": { - "message": "Public API", + "message": "API pública", "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Show character count" + "message": "Mostrar contagem de caracteres" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Esconder contagem de caracteres" }, "editAccess": { - "message": "Edit access" + "message": "Editar acesso" }, "textHelpText": { "message": "Utilize campos de texto para dados como perguntas de segurança" @@ -9540,23 +9838,23 @@ "message": "Tem certeza de que deseja excluir permanentemente esse anexo?" }, "manageSubscriptionFromThe": { - "message": "Manage subscription from the", + "message": "Gerenciar assinatura do", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." + "message": "Para hospedar o Bitwarden no seu próprio servidor, você precisará enviar seu arquivo de licença. Para apoiar planos de famílias gratuitas e recursos avançados de faturamento para sua organização auto-hospedada, você precisará configurar a sincronização automática em sua organização auto-hospedada." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "Auto-hospedado" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { - "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." + "message": "Membros não conformes serão revogados. Os administradores podem restaurar os membros assim que saírem de todas as outras organizações." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Excluir $NAME$", "placeholders": { "name": { "content": "$1", @@ -9565,12 +9863,22 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Isto irá excluir permanentemente todos os itens pertencentes a $NAME$. Os itens de coleção não são impactados.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Isto irá excluir permanentemente todos os itens pertencentes a $NAME$. Os itens de coleção não são impactados.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ Excluído", "placeholders": { "name": { "content": "$1", @@ -9579,6 +9887,254 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "O usuário foi removido da organização e todos os dados de usuários associados foram excluídos." + }, + "deletedUserId": { + "message": "Usuário excluído $ID$ - um proprietário / administrador excluiu a conta do usuário", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Usuário $ID$ deixou a organização", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "O $ORGANIZATION$ está suspenso", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Entre em contato com o proprietário da sua organização para assistência." + }, + "suspendedOwnerOrgMessage": { + "message": "Para recuperar o acesso à sua organização, adicione um método de pagamento." + }, + "deleteMembers": { + "message": "Excluir membros" + }, + "noSelectedMembersApplicable": { + "message": "Esta ação não se aplica a nenhum dos membros selecionados." + }, + "deletedSuccessfully": { + "message": "Excluído com sucesso" + }, + "freeFamiliesSponsorship": { + "message": "Remova o patrocínio das famílias do Bitwarden" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Não permita que os membros resgatem um plano de Famílias por meio desta organização." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você será obrigado a verificar sua conta bancária. Nós faremos um micro-depósito nos próximos 1-2 dias úteis. Digite o código do descritor da instrução a partir deste depósito na página de faturamento da organização para verificar a conta bancária. A não verificação da conta bancária resultará em um pagamento não atendido e sua assinatura será suspensa." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Fizemos um microdepósito em sua conta bancária (isso pode levar 1-2 dias úteis). Digite o código de seis dígitos começando com 'SM' encontrado na descrição de depósito. A não verificação da conta bancária resultará em um pagamento não atendido e sua assinatura será suspensa." + }, + "descriptorCode": { + "message": "Código do descritor" + }, + "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" + }, + "removeMembers": { + "message": "Remover membro?" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index a335b157978..637c65f644d 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Aplicações críticas" }, + "accessIntelligence": { + "message": "Aceder à informação" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Perceções do risco" }, "passwordRisk": { "message": "Risco da palavra-passe" }, - "discoverAtRiskPasswords": { - "message": "Descubra as palavras-passe em risco e notifique os utilizadores para alterarem essas palavras-passe." + "reviewAtRiskPasswords": { + "message": "Reveja as palavras-passe em risco (fracas, expostas ou reutilizadas) em todas as aplicações. Selecione as suas aplicações mais críticas para dar prioridade a ações de segurança para os seus utilizadores para resolver as palavras-passe em risco." }, "dataLastUpdated": { "message": "Última atualização dos dados: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Membros notificados" }, + "revokeMembers": { + "message": "Revogar membros" + }, + "restoreMembers": { + "message": "Restaurar membros" + }, + "cannotRestoreAccessError": { + "message": "Não é possível restaurar o acesso à organização" + }, "allApplicationsWithCount": { "message": "Todas as aplicações ($COUNT$)", "placeholders": { @@ -36,7 +48,7 @@ } }, "createNewLoginItem": { - "message": "Criar novo item de credencial" + "message": "Criar nova credencial" }, "criticalApplicationsWithCount": { "message": "Aplicações críticas ($COUNT$)", @@ -75,10 +87,10 @@ "message": "Selecione as suas aplicações mais críticas para descobrir palavras-passe em risco e notifique os utilizadores para alterarem essas palavras-passe." }, "markCriticalApps": { - "message": "Marcar aplicações críticas" + "message": "Marcar apps críticas" }, "markAppAsCritical": { - "message": "Marcar a aplicação como crítica" + "message": "Marcar a app como crítica" }, "appsMarkedAsCritical": { "message": "Apps marcadas como críticas" @@ -490,7 +502,7 @@ "message": "Eliminar" }, "favorite": { - "message": "Favorito" + "message": "Adicionar aos favoritos" }, "unfavorite": { "message": "Remover dos favoritos" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Nota segura" }, + "typeSshKey": { + "message": "Chave SSH" + }, "typeLoginPlural": { "message": "Credenciais" }, @@ -931,7 +946,7 @@ "message": "Nível de acesso" }, "accessing": { - "message": "A aceder" + "message": "A aceder a" }, "loggedOut": { "message": "Sessão terminada" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Precisa de outra opção?" }, + "needAnotherOptionV1": { + "message": "Precisa de outra opção?" + }, "loginWithMasterPassword": { "message": "Iniciar sessão com a palavra-passe mestra" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Iniciar sessão no Bitwarden" }, + "authenticationTimeout": { + "message": "Tempo limite de autenticação" + }, + "authenticationSessionTimedOut": { + "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de início de sessão." + }, "verifyIdentity": { "message": "Verifique a sua identidade" }, + "whatIsADevice": { + "message": "O que é um dispositivo?" + }, + "aDeviceIs": { + "message": "Um dispositivo é uma instalação única da app Bitwarden onde o utilizador iniciou sessão. Reinstalar, limpar os dados da aplicação ou limpar os seus cookies pode fazer com que um dispositivo apareça várias vezes." + }, "logInInitiated": { "message": "A preparar o início de sessão" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Foi enviada uma notificação para o seu dispositivo." }, + "aNotificationWasSentToYourDevice": { + "message": "Foi enviada uma notificação para o seu dispositivo" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Certifique-se de que a sua conta está desbloqueada e que a frase de impressão digital corresponde à do outro dispositivo" + }, "versionNumber": { "message": "Versão $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Histórico de palavras-passe" }, + "generatorHistory": { + "message": "Histórico do gerador" + }, + "clearGeneratorHistoryTitle": { + "message": "Limpar o histórico do gerador" + }, + "cleargGeneratorHistoryDescription": { + "message": "Se continuar, todas as entradas serão permanentemente eliminadas do histórico do gerador. Tem a certeza de que pretende continuar?" + }, "noPasswordsInList": { "message": "Não existem palavras-passe para listar." }, + "clearHistory": { + "message": "Limpar histórico" + }, + "nothingToShow": { + "message": "Nada a mostrar" + }, + "nothingGeneratedRecently": { + "message": "Não gerou nada recentemente" + }, "clear": { "message": "Limpar", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Por favor, inicie sessão novamente." }, + "currentSession": { + "message": "Sessão atual" + }, + "requestPending": { + "message": "Pedido pendente" + }, "logBackInOthersToo": { "message": "Por favor, inicie sessão novamente. Se estiver a utilizar outras aplicações Bitwarden, termine a sessão e volte a iniciar sessão nessas também." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Todas as sessões desautorizadas" }, - "accountIsManagedMessage": { - "message": "Esta conta é gerida por $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Esta conta é propriedade de $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ainda tem 1 convite." + }, "userUsingTwoStep": { "message": "Este utilizador está a utilizar a verificação de dois passos para proteger a sua conta." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Ver todas as opções de início de sessão" + }, "viewAllLoginOptions": { "message": "Ver todas as opções de início de sessão" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Estado do início de sessão" + }, + "firstLogin": { + "message": "Primeiro início de sessão" + }, + "trusted": { + "message": "Confiável" + }, "creatingAccountOn": { "message": "A criar conta em" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Atualizar navegador" }, + "generatingRiskInsights": { + "message": "A gerar as suas perceções de riscos..." + }, "updateBrowserDesc": { "message": "Está a utilizar um navegador web não suportado. O cofre web pode não funcionar corretamente." }, + "freeTrialEndPromptCount": { + "message": "O seu período experimental gratuito termina dentro de $COUNT$ dias.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, o seu período experimental gratuito termina dentro de $COUNT$ dias.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, o seu período experimental gratuito termina amanhã.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "O seu período experimental gratuito termina amanhã." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, o seu período experimental gratuito termina hoje.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "O seu período experimental gratuito termina hoje." + }, + "clickHereToAddPaymentMethod": { + "message": "Clique aqui para adicionar um método de pagamento." + }, "joinOrganization": { "message": "Aderir à organização" }, @@ -4388,6 +4515,9 @@ "message": "Nunca solicitar a verificação de frases de impressões digitais para utilizadores convidados (não recomendado)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Será notificado quando o pedido for aprovado" + }, "free": { "message": "Gratuito", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Inicie sessão utilizando o portal de início de sessão único da sua organização. Por favor, introduza o identificador SSO da sua organização para começar." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Introduza o identificador SSO da sua organização para começar" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Para iniciar sessão com o seu fornecedor de SSO, introduza o identificador de SSO da sua organização para começar. Poderá ser necessário introduzir este identificador SSO quando iniciar sessão a partir de um novo dispositivo." + }, "enterpriseSingleSignOn": { "message": "Início de sessão único para empresas" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluído, não aplicável a esta ação" }, + "nonCompliantMembersTitle": { + "message": "Membros não conformes" + }, + "nonCompliantMembersError": { + "message": "Os membros que não estejam em conformidade com a política de organização única ou de verificação de dois passos não podem ser restaurados até cumprirem os requisitos da política" + }, "fingerprint": { "message": "Impressão digital" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A permissão para gerir utilizadores deve também ser concedida com a permissão para gerir a recuperação da conta" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-mail enviado" }, - "revokeSponsorshipConfirmation": { - "message": "Depois de remover esta conta, o patrocínio do plano Familiar expirará no final do período de faturação. Não será possível resgatar uma nova oferta de patrocínio até que a existente expire. Tem a certeza de que pretende continuar?" - }, "removeSponsorshipSuccess": { "message": "Patrocínio removido" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Necessário se o ID da entidade não for um URL." }, + "offerNoLongerValid": { + "message": "Esta oferta já não é válida. Contacte os administradores da sua organização para obter mais informações." + }, "openIdOptionalCustomizations": { "message": "Personalizações opcionais" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Gerar e-mail" }, - "generatorBoundariesHint": { - "message": "O valor deve estar entre $MIN$ e $MAX$", + "spinboxBoundariesHint": { + "message": "O valor deve estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Utilize $RECOMMENDED$ caracteres ou mais para gerar uma palavra-passe forte.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Utilize $RECOMMENDED$ palavras ou mais para gerar uma frase de acesso 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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tipo de nome de utilizador" }, @@ -6681,6 +6857,10 @@ "message": "Aprovisione automaticamente utilizadores e grupos com o seu fornecedor de identidade preferido através do aprovisionamento SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Aprovisione automaticamente utilizadores e grupos com o seu fornecedor de identidade preferido através do aprovisionamento SCIM. Encontre integrações suportadas", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Ativar SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "A preparar o início de sessão" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Memorizar este dispositivo para facilitar futuros inícios de sessão" + }, "deviceApprovalRequired": { "message": "É necessária a aprovação do dispositivo. Selecione uma opção de aprovação abaixo:" }, + "deviceApprovalRequiredV2": { + "message": "Aprovação do dispositivo necessária" + }, + "selectAnApprovalOptionBelow": { + "message": "Selecione uma opção de aprovação abaixo" + }, "rememberThisDevice": { "message": "Lembrar este dispositivo" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Aprovar pedido" }, + "deviceApproved": { + "message": "Dispositivo aprovado" + }, + "deviceRemoved": { + "message": "Dispositivo removido" + }, + "removeDevice": { + "message": "Remover dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Tem a certeza de que pretende remover este dispositivo?" + }, "noDeviceRequests": { "message": "Sem pedidos de dispositivos" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "E-mail do utilizador em falta" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "O e-mail do utilizador ativo não foi encontrado. A terminar a sessão." + }, "deviceTrusted": { "message": "Dispositivo de confiança" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Gerir o comportamento da coleção da organização" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limitar a criação e eliminação de coleções aos proprietários e administradores" - }, "limitCollectionCreationDesc": { "message": "Limitar a criação de coleções aos proprietários e administradores" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "adicione um método de pagamento", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informações sobre a organização" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Utilize o SDK do Gestor de Segredos do Bitwarden nas seguintes linguagens de programação para criar as suas próprias aplicações." }, - "setUpGithubActions": { - "message": "Configurar ações do GitHub" + "ssoDescStart": { + "message": "Configurar", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "para o Bitwarden utilizando o guia de implementação do seu fornecedor de identidade.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Provisionamento de utilizadores" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Configure o ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Configurar o Kubernetes" + "scimIntegrationDescEnd": { + "message": "(Sistema de Gestão de Identidades entre Domínios) para fornecer automaticamente utilizadores e grupos ao Bitwarden utilizando o guia de implementação do seu fornecedor de identidade.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Configurar o GitLab CI/CD" + "bwdc": { + "message": "Conector de diretório do Bitwarden" }, - "setUpAnsible": { - "message": "Configurar o Ansible" + "bwdcDesc": { + "message": "Configure o Conector de diretório Bitwarden para aprovisionar automaticamente utilizadores e grupos utilizando o guia de implementação do seu fornecedor de identidade." }, - "rustSDKRepo": { - "message": "Ver o repositório Rust" + "eventManagement": { + "message": "Gestão de eventos" }, - "cSharpSDKRepo": { - "message": "Ver repositório de C#" + "eventManagementDesc": { + "message": "Integre os registos de eventos Bitwarden com o seu sistema SIEM (Informações do Sistema e Gestão de Eventos) utilizando o guia de implementação da sua plataforma." }, - "cPlusPlusSDKRepo": { - "message": "Ver repositório de C++" + "deviceManagement": { + "message": "Gestão de dispositivos" }, - "jsWebAssemblySDKRepo": { - "message": "Ver repositório de JS WebAssembly" + "deviceManagementDesc": { + "message": "Configure a gestão de dispositivos do Bitwarden utilizando o guia de implementação da sua plataforma." }, - "javaSDKRepo": { - "message": "Ver repositório de Java" + "integrationCardTooltip": { + "message": "Lançar o guia de implementação da $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Configurar a $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Ver repositório de Python" + "smSdkTooltip": { + "message": "Ver o repositório $SDK$", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Ver repositório de php" + "integrationCardAriaLabel": { + "message": "abrir o guia de implementação da $INTEGRATION$ num novo separador.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Ver repositório de Ruby" + "smSdkAriaLabel": { + "message": "ver o repositório $SDK$ num novo separador.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Ver repositório de Go" + "smIntegrationCardAriaLabel": { + "message": "configurar o guia de implementação da $INTEGRATION$ num novo separador.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Crie uma nova organização de clientes para gerir como Fornecedor. Os lugares adicionais serão refletidos na próxima faturação." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Fornecedor de serviços geridos" }, + "managedServiceProvider": { + "message": "Fornecedor de serviços geridos" + }, + "multiOrganizationEnterprise": { + "message": "Empresa multiorganizacional" + }, "orgSeats": { "message": "Lugares da organização" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Informações fiscais atualizadas" }, + "billingInvalidTaxIdError": { + "message": "Número de identificação fiscal inválido. Se considerar que se trata de um erro, contacte a assistência." + }, + "billingTaxIdTypeInferenceError": { + "message": "Não foi possível validar o seu número de identificação fiscal. Se considerar que se trata de um erro, contacte a assistência." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Número de identificação fiscal inválido. Se considerar que se trata de um erro, contacte a assistência." + }, + "billingPreviewInvoiceError": { + "message": "Ocorreu um erro ao pré-visualizar a fatura. Por favor, tente novamente mais tarde." + }, "unverified": { "message": "Não verificado" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB de armazenamento adicional" }, + "sshKeyAlgorithm": { + "message": "Algoritmo de chaves" + }, + "sshKeyFingerprint": { + "message": "Impressão digital" + }, + "sshKeyPrivateKey": { + "message": "Chave privada" + }, + "sshKeyPublicKey": { + "message": "Chave pública" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 contas Premium" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Auto-hospedagem" }, - "verified-domain-single-org-warning": { - "message": "A verificação de um domínio ativará a política de organização única." + "claim-domain-single-org-warning": { + "message": "A reivindicação de um domínio ativará a política de organização única." }, "single-org-revoked-user-warning": { "message": "Os membros não conformes serão revogados. Os administradores podem restaurar os membros quando estes saírem de todas as outras organizações." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Quando um membro é eliminado, a sua conta Bitwarden e os dados individuais do cofre serão permanentemente eliminados. Os dados da coleção permanecerão na organização. Para os reintegrar, têm de criar uma conta e ser novamente integrados.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Esta ação eliminará permanentemente todos os itens pertencentes a $NAME$. Os itens da coleção não são afetados.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Esta ação eliminará permanentemente todos os itens pertencentes aos seguintes membros. Os itens da coleção não são afetados.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "$NAME$ eliminado", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "O utilizador foi removido da organização e todos os dados de utilizador associados foram eliminados." + }, + "deletedUserId": { + "message": "Utilizador $ID$ eliminado - um proprietário/administrador eliminou a conta de utilizador", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "O utilizador $ID$ saiu da organização", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "A $ORGANIZATION$ está suspensa", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contacte o proprietário da sua organização para obter assistência." + }, + "suspendedOwnerOrgMessage": { + "message": "Para recuperar o acesso à sua organização, adicione um método de pagamento." + }, + "deleteMembers": { + "message": "Eliminar membros" + }, + "noSelectedMembersApplicable": { + "message": "Esta ação não é aplicável a nenhum dos membros selecionados." + }, + "deletedSuccessfully": { + "message": "Eliminados com sucesso" + }, + "freeFamiliesSponsorship": { + "message": "Remover o patrocínio do plano Bitwarden Familiar Gratuito" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Não permitir que os membros resgatem um plano Familiar através desta organização." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Ser-lhe-á pedido que verifique a sua conta bancária. Efetuaremos um micro-depósito nos próximos 1-2 dias úteis. Introduza o código descritor do extrato deste depósito na página de faturação da organização para verificar a conta bancária. A não verificação da conta bancária resultará na falta de pagamento e na suspensão da sua subscrição." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Efetuámos um micro-depósito na sua conta bancária (pode demorar 1-2 dias úteis). Introduza o código de seis dígitos que começa por \"SM\" e que se encontra na descrição do depósito. Se a conta bancária não for verificada, o pagamento não será efetuado e a sua subscrição será suspensa." + }, + "descriptorCode": { + "message": "Código descritor" + }, + "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" + }, + "removeMembers": { + "message": "Remover membros" + }, + "devices": { + "message": "Dispositivos" + }, + "deviceListDescription": { + "message": "A sua conta foi iniciada em cada um dos dispositivos abaixo. Se não reconhecer um dispositivo, remova-o agora." + }, + "deviceListDescriptionTemp": { + "message": "Tem sessão iniciada em cada um dos dispositivos abaixo." + }, + "claimedDomains": { + "message": "Domínios reivindicados" + }, + "claimDomain": { + "message": "Reivindicar domínio" + }, + "reclaimDomain": { + "message": "Recuperar domínio" + }, + "claimDomainNameInputHint": { + "message": "Exemplo: omeudominio.com. Os subdomínios requerem campos separados para serem reivindicados." + }, + "automaticClaimedDomains": { + "message": "Domínios reivindicados automaticamente" + }, + "automaticDomainClaimProcess": { + "message": "O Bitwarden tentará reivindicar o domínio 3 vezes durante as primeiras 72 horas. Se o domínio não puder ser reivindicado, verifique o registo DNS no seu anfitrião e reivindique manualmente. O domínio será removido da sua organização em 7 dias se não for reivindicado." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ não reivindicado. Verifique o seu registo DNS.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Reivindicado" + }, + "domainStatusUnderVerification": { + "message": "Sob verificação" + }, + "claimedDomainsDesc": { + "message": "Reivindique um domínio para possuir todas as contas de membros cujo endereço de e-mail corresponda ao domínio. Os membros poderão ignorar o identificador SSO quando iniciarem sessão. Os administradores também poderão eliminar contas de membros." + }, + "invalidDomainNameClaimMessage": { + "message": "O campo não é um formato válido. Formato: omeudominio.com. Os subdomínios requerem campos separados para serem reivindicado." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ reivindicado", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ não reivindicado", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Se remover $EMAIL$, o patrocínio deste plano Familiar não pode ser resgatado. Tem a certeza de que pretende continuar?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Se remover $EMAIL$, o patrocínio deste plano Familiar terminará e o método de pagamento guardado será cobrado $40 + imposto aplicável a $DATE$. Não será possível resgatar um novo patrocínio até $DATE$. Tem a certeza de que pretende continuar?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domínio reivindicado" + }, + "organizationNameMaxLength": { + "message": "O nome da organização não pode exceder 50 caracteres." + }, + "resellerRenewalWarning": { + "message": "A sua subscrição será renovada em breve. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "A fatura da sua subscrição foi emitida a $ISSUED_DATE$. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "A fatura da sua subscrição não foi paga. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Subscrição da organização reiniciada" + }, + "restartSubscription": { + "message": "Reinicie a sua subscrição" + }, + "suspendedManagedOrgMessage": { + "message": "Contacte a $PROVIDER$ para obter assistência.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 8f57c764c5e..42d13688444 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1,9 +1,12 @@ { "allApplications": { - "message": "All applications" + "message": "Toate aplicațiile" }, "criticalApplications": { - "message": "Critical applications" + "message": "Aplicațiile critice" + }, + "accessIntelligence": { + "message": "Access Intelligence" }, "riskInsights": { "message": "Risk Insights" @@ -11,8 +14,8 @@ "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Notă securizată" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Conectări" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Autentificați-vă cu parola principală" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Autentificare inițiată" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "O notificare a fost trimisă pe dispozitivul dvs." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Versiunea $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Istoric parole" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Nicio parolă de afișat." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Ștergere", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Vă rugăm să vă conectați din nou." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vă rugăm să vă reconectați. Dacă utilizați și alte aplicații Bitwarden, reconectați-vă la ele de asemenea." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Toate sesiunile au fost dezautorizate" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Acest utilizator folosește conectarea în două etape pentru a-și proteja contul." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Vedeți toate opțiunile de autentificare" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Dispozitiv" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Actualizare browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Utilizați un browser nesuportat. Seiful web ar putea să nu funcționeze corect." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Alăturare la organizație" }, @@ -4388,6 +4515,9 @@ "message": "Nu solicitați niciodată verificarea frazelor amprentă pentru utilizatorii invitați (nerecomandat)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratuit", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Conectați-vă utilizând portalul de conectare unică al organizației. Pentru a începe, Introduceți vă rog identificatorul organizației dvs." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Autentificare unică întreprindere" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Exclus, nu se aplică pentru această acțiune" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Amprentă" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-mail trimis" }, - "revokeSponsorshipConfirmation": { - "message": "După eliminarea acestui cont, sponsorizarea planului Familii va expira la sfârșitul perioadei de facturare. Nu veți putea revendica o nouă ofertă de sponsorizare până când nu expiră cea existentă. Sunteți sigur că doriți să continuați?" - }, "removeSponsorshipSuccess": { "message": "Sponsorizare înlăturată" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Necesar dacă ID-ul de entitate nu este un URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Personalizări opționale" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Tip de nume de utilizator" }, @@ -6681,6 +6857,10 @@ "message": "Furnizați automat utilizatorii și grupurile cu furnizorul dvs. preferat de identitate prin intermediul aprovizionării SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Activați SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "View Rust repository" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "View Java repository" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "View Python repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "View php repository" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "View Ruby repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "View Go repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 21928393746..28386190d17 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Критичные приложения" }, + "accessIntelligence": { + "message": "Управление доступом" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Информация о рисках" }, "passwordRisk": { "message": "Риск пароля" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Проанализируйте пароли, подверженные риску (слабые, скомпрометированные или повторно используемые), во всех приложениях. Выберите наиболее важные приложения, чтобы определить приоритетные меры безопасности для пользователей, направленные на устранение подверженных риску паролей." }, "dataLastUpdated": { "message": "Последнее обновление: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Уведомленные участники" }, + "revokeMembers": { + "message": "Отзыв пользователей" + }, + "restoreMembers": { + "message": "Восстановление пользователей" + }, + "cannotRestoreAccessError": { + "message": "Невозможно восстановить доступ к организации" + }, "allApplicationsWithCount": { "message": "Все приложения ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Защищенная заметка" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "typeLoginPlural": { "message": "Логины" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Вход с устройства должен быть настроен в настройках мобильного приложения Bitwarden. Нужен другой вариант?" }, + "needAnotherOptionV1": { + "message": "Нужен другой вариант?" + }, "loginWithMasterPassword": { "message": "Войти с мастер-паролем" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Войти в Bitwarden" }, + "authenticationTimeout": { + "message": "Таймаут аутентификации" + }, + "authenticationSessionTimedOut": { + "message": "Сеанс аутентификации завершился по времени. Пожалуйста, попробуйте войти еще раз." + }, "verifyIdentity": { "message": "Подтвердите вашу личность" }, + "whatIsADevice": { + "message": "Что такое устройство?" + }, + "aDeviceIs": { + "message": "Устройство - это уникальная установка приложения Bitwarden, в которой вы авторизовались. Переустановка, очистка данных приложения или очистка файлов cookie может привести к тому, что устройство будет отображаться несколько раз." + }, "logInInitiated": { "message": "Вход инициирован" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "На ваше устройство отправлено уведомление." }, + "aNotificationWasSentToYourDevice": { + "message": "На ваше устройство было отправлено уведомление" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Убедитесь, что ваш аккаунт разблокирован и фраза отпечатка совпадает с фразой на другом устройстве" + }, "versionNumber": { "message": "Версия $VERSION_NUMBER$", "placeholders": { @@ -1548,7 +1584,7 @@ "message": "Ограничено аккаунтом" }, "passwordProtected": { - "message": "Пароль защищен" + "message": "Защищено паролем" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Пароль к файлу\" и \"Подтверждение пароля к файлу\" не совпадают." @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "История паролей" }, + "generatorHistory": { + "message": "История генератора" + }, + "clearGeneratorHistoryTitle": { + "message": "Очистить историю генератора" + }, + "cleargGeneratorHistoryDescription": { + "message": "Если вы продолжите, все записи будут навсегда удалены из истории генератора. Вы уверены, что хотите продолжить?" + }, "noPasswordsInList": { "message": "Нет паролей для отображения." }, + "clearHistory": { + "message": "Очистить историю" + }, + "nothingToShow": { + "message": "Нечего показать" + }, + "nothingGeneratedRecently": { + "message": "Вы ничего не создавали в последнее время" + }, "clear": { "message": "Очистить", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Пожалуйста, войдите снова." }, + "currentSession": { + "message": "Текущая сессия" + }, + "requestPending": { + "message": "Запрос в ожидании" + }, "logBackInOthersToo": { "message": "Пожалуйста, войдите снова. Если вы используете другие приложения Bitwarden, выполните на них выход и повторный вход." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Все сессии деавторизованы" }, - "accountIsManagedMessage": { - "message": "Управление этим аккаунтом осуществляет $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Этот аккаунт принадлежит $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -2357,7 +2417,7 @@ "message": "Проверить пароли на компрометацию" }, "timesExposed": { - "message": "Times exposed" + "message": "Количество утечек" }, "exposedXTimes": { "message": "Скомпрометирован $COUNT$ раз(а)", @@ -2394,7 +2454,7 @@ "message": "В вашем хранилище нет слабых паролей." }, "weakness": { - "message": "Weakness" + "message": "Уязвимость" }, "reusedPasswordsReport": { "message": "Повторно использованные пароли" @@ -2422,7 +2482,7 @@ "message": "В вашем хранилище нет повторно использованных паролей." }, "timesReused": { - "message": "Times reused" + "message": "Повторных использований" }, "reusedXTimes": { "message": "Повторно использован $COUNT$ раз(а)", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "У вас осталось 1 приглашение." + }, "userUsingTwoStep": { "message": "Этот пользователь использует двухэтапную аутентификацию для защиты своего аккаунта." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Посмотреть все варианты авторизации" + }, "viewAllLoginOptions": { "message": "Посмотреть все варианты авторизации" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Статус входа" + }, + "firstLogin": { + "message": "Первый вход" + }, + "trusted": { + "message": "Доверенный" + }, "creatingAccountOn": { "message": "Создание аккаунта" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Обновить браузер" }, + "generatingRiskInsights": { + "message": "Генерирование информации о рисках..." + }, "updateBrowserDesc": { "message": "Вы используете неподдерживаемый браузер. Веб-хранилище может работать некорректно." }, + "freeTrialEndPromptCount": { + "message": "Ваша бесплатная пробная версия заканчивается через $COUNT$ дней.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, ваша бесплатная пробная версия заканчивается через $COUNT$ дней.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, ваша бесплатная пробная версия заканчивается завтра.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Ваша бесплатная пробная версия заканчивается завтра." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, ваша бесплатная пробная версия заканчивается сегодня.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Ваша бесплатная пробная версия заканчивается сегодня." + }, + "clickHereToAddPaymentMethod": { + "message": "Нажмите здесь, чтобы добавить способ оплаты." + }, "joinOrganization": { "message": "Присоединиться к организации" }, @@ -4388,6 +4515,9 @@ "message": "Никогда не запрашивать верификацию фразы отпечатка для приглашенных пользователей (не рекомендуется)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Вы получите уведомление, когда запрос будет одобрен" + }, "free": { "message": "Бесплатно", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Авторизуйтесь при помощи единого корпоративного портала. Чтобы начать, введите идентификатор вашей организации." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Чтобы начать, введите идентификатор SSO вашей организации" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Для авторизации при помощи вашего провайдера SSO, введите идентификатор SSO вашей организации. Этот идентификатор SSO может потребоваться при авторизации с нового устройства." + }, "enterpriseSingleSignOn": { "message": "Единая корпоративная авторизация" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Исключено, не применимо для данного действия." }, + "nonCompliantMembersTitle": { + "message": "Участники, не отвечающие требованиям" + }, + "nonCompliantMembersError": { + "message": "Участники, не соответствующие требованиям политики единой организации или двухэтапной аутентификации, не могут быть восстановлены, пока они не будут соответствовать требованиям политики" + }, "fingerprint": { "message": "Отпечаток" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для управления пользователями также необходимо предоставить разрешение на управление восстановлением аккаунта" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Письмо отправлено" }, - "revokeSponsorshipConfirmation": { - "message": "После удаления этого аккаунта спонсирование плана Families истечет в конце расчетного периода. Вы не сможете воспользоваться новым спонсорским предложением, пока не истечет срок действия существующего. Вы уверены, что хотите продолжить?" - }, "removeSponsorshipSuccess": { "message": "Спонсирование удалено" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Обязательно, если ID сущности не является URL." }, + "offerNoLongerValid": { + "message": "Это предложение больше не действует. Для получения дополнительной информации обратитесь к администраторам вашей организации." + }, "openIdOptionalCustomizations": { "message": "Дополнительные настройки" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Сгенерировать email" }, - "generatorBoundariesHint": { - "message": "Значение должно быть между $MIN$ и $MAX$", + "spinboxBoundariesHint": { + "message": "Значение должно быть между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип имени пользователя" }, @@ -6681,6 +6857,10 @@ "message": "Автоматически предоставлять пользователям и группам предпочитаемого поставщика удостоверений с помощью обеспечения SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Автоматически предоставлять пользователям и группам предпочитаемого поставщика удостоверений с помощью обеспечения SCIM. Поиск поддерживаемых интеграций", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Включить SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Вход инициирован" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запомнить это устройство, чтобы в будущем авторизовываться быстрее" + }, "deviceApprovalRequired": { "message": "Требуется одобрение устройства. Выберите вариант ниже:" }, + "deviceApprovalRequiredV2": { + "message": "Требуется подтверждение устройства" + }, + "selectAnApprovalOptionBelow": { + "message": "Выберите вариант подтверждения ниже" + }, "rememberThisDevice": { "message": "Запомнить это устройство" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Одобрить запрос" }, + "deviceApproved": { + "message": "Устройство одобрено" + }, + "deviceRemoved": { + "message": "Устройство удалено" + }, + "removeDevice": { + "message": "Удалить устройство" + }, + "removeDeviceConfirmation": { + "message": "Вы уверены, что хотите удалить это устройство?" + }, "noDeviceRequests": { "message": "Нет запросов устройств" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Отсутствует email пользователя" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Email активного пользователя не найден. Разлогиниваем." + }, "deviceTrusted": { "message": "Доверенное устройство" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Управление настройками коллекций для организации" }, - "limitCollectionCreationDeletionDesc": { - "message": "Ограничить создание и удаление коллекций владельцам и администраторам" - }, "limitCollectionCreationDesc": { "message": "Ограничить создание коллекций владельцам и администраторам" }, @@ -8325,7 +8526,7 @@ "message": "Максимальная потенциальная стоимость сервисного аккаунта" }, "loggedInExclamation": { - "message": "Вход выполнен!" + "message": "Выполнен вход!" }, "beta": { "message": "Beta" @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "добавьте способ оплаты", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Информация об организации" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Используйте SDK Bitwarden Secrets Manager на следующих языках программирования для создания собственных приложений." }, - "setUpGithubActions": { - "message": "Настроить Github Actions" + "ssoDescStart": { + "message": "Настроить", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "для Bitwarden, используя руководство по внедрению для вашего поставщика идентификационных данных.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Развертывание пользователя" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Настроить ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Настроить Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) для автоматического развертывания пользователей и групп в Bitwarden, используя руководство по внедрению вашего провайдера идентификации.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Настроить GitLab CI/CD" + "bwdc": { + "message": "Коннектор каталогов Bitwarden" }, - "setUpAnsible": { - "message": "Настроить Ansible" + "bwdcDesc": { + "message": "Настройте Коннектор каталогов Bitwarden на автоматическое развертывание пользователей и групп, используя руководство по внедрению для вашего провайдера идентификации." }, - "rustSDKRepo": { - "message": "Просмотр репозитория Rust" + "eventManagement": { + "message": "Управление событиями" }, - "cSharpSDKRepo": { - "message": "Просмотр репозитория C#" + "eventManagementDesc": { + "message": "Интегрируйте журналы событий Bitwarden с вашей системой SIEM (управление системной информацией и событиями), используя руководство по внедрению для вашей платформы." }, - "cPlusPlusSDKRepo": { - "message": "Просмотр репозитория C++" + "deviceManagement": { + "message": "Управление устройством" }, - "jsWebAssemblySDKRepo": { - "message": "Просмотр репозитория JS WebAssembly" + "deviceManagementDesc": { + "message": "Настройте управление устройствами для Bitwarden, используя руководство по внедрению для вашей платформы." }, - "javaSDKRepo": { - "message": "Просмотр репозитория Java" + "integrationCardTooltip": { + "message": "Запустить руководство по внедрению $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Настроить $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Просмотр репозитория Python" + "smSdkTooltip": { + "message": "Просмотр репозитория $SDK$", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Просмотр репозитория php" + "integrationCardAriaLabel": { + "message": "откройте руководство по внедрению $INTEGRATION$ в новой вкладке.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Просмотр репозитория Ruby" + "smSdkAriaLabel": { + "message": "просмотр репозитория $SDK$ в новой вкладке.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Просмотр репозитория Go" + "smIntegrationCardAriaLabel": { + "message": "настроить руководство по внедрению $INTEGRATION$ в новой вкладке.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Создайте новую клиентскую организацию для управления ею в качестве провайдера. Дополнительные места будут отражены в следующем биллинговом цикле." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Поставщик управляемых услуг" }, + "managedServiceProvider": { + "message": "Поставщик управляемых услуг" + }, + "multiOrganizationEnterprise": { + "message": "Многоорганизационное предприятие" + }, "orgSeats": { "message": "Места организации" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Обновление сведений о налогах" }, + "billingInvalidTaxIdError": { + "message": "Недействительный ID, если вы считаете, что это ошибка, обратитесь в службу поддержки." + }, + "billingTaxIdTypeInferenceError": { + "message": "Мы не смогли подтвердить ваш ID, если вы считаете, что это ошибка, обратитесь в службу поддержки." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Недействительный ID, если вы считаете, что это ошибка, обратитесь в службу поддержки." + }, + "billingPreviewInvoiceError": { + "message": "При подготовке счета произошла ошибка. Пожалуйста, повторите попытку позже." + }, "unverified": { "message": "Неверифицирован" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "Дополнительное место в хранилище" }, + "sshKeyAlgorithm": { + "message": "Алгоритм ключа" + }, + "sshKeyFingerprint": { + "message": "Отпечаток" + }, + "sshKeyPrivateKey": { + "message": "Приватный ключ" + }, + "sshKeyPublicKey": { + "message": "Публичный ключ" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 Премиум-аккаунтов" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Собственное размещение" }, - "verified-domain-single-org-warning": { - "message": "Проверка домена включит политику единой организации." + "claim-domain-single-org-warning": { + "message": "При регистрации домена будет включена политика единой организации." }, "single-org-revoked-user-warning": { "message": "Участники, не соблюдающие требования, будут аннулированы. Администраторы могут восстановить участников, как только они покинут все другие организации." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "При удалении участника его аккаунт Bitwarden и личные данные хранилища будут удалены навсегда. Данные коллекций останутся в организации. Чтобы восстановить их, необходимо создать аккаунт и заново пройти процедуру регистрации.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Это приведет к окончательному удалению всех элементов, принадлежащих $NAME$. Элементы коллекции не затрагиваются.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Это приведет к безвозвратному удалению всех предметов, принадлежащих следующим пользователям. Элементы коллекций не затрагиваются.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Аккаунт $NAME$ удален", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Пользователь был удален из организации, и все связанные с ним данные были удалены." + }, + "deletedUserId": { + "message": "Владелец - удаленный пользователь $ID$ / администратор удалил аккаунт пользователя", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Пользователь $ID$ покинул организацию", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ приостановлена", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Обратитесь за помощью к владельцу организации." + }, + "suspendedOwnerOrgMessage": { + "message": "Чтобы восстановить доступ к своей организации, добавьте способ оплаты." + }, + "deleteMembers": { + "message": "Удалить участников" + }, + "noSelectedMembersApplicable": { + "message": "Это действие не применимо ни к одному из выбранных участников." + }, + "deletedSuccessfully": { + "message": "Успешно удален" + }, + "freeFamiliesSponsorship": { + "message": "Удалить спонсорство Free Bitwarden Families" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Не разрешать участникам выкупать план Families через эту организацию." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Оплата с помощью банковского счета доступна только для клиентов в США. Вам потребуется подтвердить свой банковский счет. Мы сделаем микродепозит в течение следующих 1-2 рабочих дней. Введите код дескриптора выписки из этого депозита на странице выставления счетов организации, чтобы подтвердить банковский счет. Неподтверждение банковского счета приведет к пропуску платежа и приостановке подписки." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Мы перевели микродепозит на ваш банковский счет (это может занять 1-2 рабочих дня). Введите шестизначный код, начинающийся с 'SM', который указан в описании депозита. Если вы не подтвердите банковский счет, это приведет к пропуску платежа и приостановке подписки." + }, + "descriptorCode": { + "message": "Код дескриптора" + }, + "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 аккаунта" + }, + "removeMembers": { + "message": "Удалить участников" + }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Ваш аккаунт был авторизован на каждом из перечисленных ниже устройств. Если устройство вам не знакомо, удалите его сейчас." + }, + "deviceListDescriptionTemp": { + "message": "Ваш аккаунт авторизован на перечисленных ниже устройствах." + }, + "claimedDomains": { + "message": "Зарегистрированные домены" + }, + "claimDomain": { + "message": "Зарегистрировать домен" + }, + "reclaimDomain": { + "message": "Разрегистрировать домен" + }, + "claimDomainNameInputHint": { + "message": "Пример: mydomain.com. Для поддоменов требуются отдельные записи." + }, + "automaticClaimedDomains": { + "message": "Автоматически зарегистрированные домены" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden попытается зарегистрировать домен 3 раза в течение первых 72 часов. Если домен не удастся зарегистрировать, проверьте запись DNS на вашем хосте и зарегистрируйте вручную. Домен будет удален из вашей организации через 7 дней, если он не будет зарегистрирован." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ не зарегистрирован. Проверьте записи DNS.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Зарегистрирован" + }, + "domainStatusUnderVerification": { + "message": "Проверяется" + }, + "claimedDomainsDesc": { + "message": "Укажите домен, чтобы все аккаунты пользователей, чей адрес email совпадает с доменом, принадлежали ему. Пользователи смогут пропускать идентификатор SSO при авторизации. Администраторы также смогут удалять аккаунты пользователей." + }, + "invalidDomainNameClaimMessage": { + "message": "Введенные данные не соответствуют формату. Формат: mydomain.com. Для регистрации поддоменов необходимы отдельные записи." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ зарегистрирован", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ не зарегистрирован", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Если вы удалите $EMAIL$, спонсорство для этого семейного плана не сможет быть выкуплено. Вы уверены, что хотите продолжить?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Если вы удалите $EMAIL$, спонсорство для этого семейного плана закончится, и с сохраненного метода оплаты будет снято $40 + применимый налог $DATE$. Вы сможете оформить новое спонсорство только после $DATE$. Вы уверены, что хотите продолжить?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Домен зарегистрирован" + }, + "organizationNameMaxLength": { + "message": "Название организации не может превышать 50 символов." + }, + "resellerRenewalWarning": { + "message": "Ваша подписка скоро будет продлена. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Счет за вашу подписку был выставлен $ISSUED_DATE$. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$, чтобы подтвердить продление подписки до $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Счет за вашу подписку не был оплачен. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Подписка организации перезапущена" + }, + "restartSubscription": { + "message": "Перезапустить подписку" + }, + "suspendedManagedOrgMessage": { + "message": "Обратитесь за помощью к $PROVIDER$.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 76be2326ec0..df5803c0fd9 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index e353e2e4605..d8adb9116ec 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kritické aplikácie" }, + "accessIntelligence": { + "message": "Prehľad o prístupe" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Prehľad o rizikách" }, "passwordRisk": { "message": "Ohrozenie hesla" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Skontrolujte ohrozené heslá (slabé, odhalené, alebo opätovne použité) naprieč aplikáciami. Vyberte najkritickejšie aplikácie a priorizujte vaším používateľom bezpečnostné opatrenia ohľadom ohrozených hesiel." }, "dataLastUpdated": { "message": "Posledná aktualizácia dát: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Odvolať členov" + }, + "restoreMembers": { + "message": "Obnoviť členov" + }, + "cannotRestoreAccessError": { + "message": "Nie je možné obnoviť prístup do organizácie" + }, "allApplicationsWithCount": { "message": "Všetky aplikácie ($COUNT$)", "placeholders": { @@ -81,7 +93,7 @@ "message": "Označiť aplikáciu ako kritickú" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "Aplikácie označené ako kritické" }, "application": { "message": "Aplikácia" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Zabezpečená poznámka" }, + "typeSshKey": { + "message": "Kľúč SSH" + }, "typeLoginPlural": { "message": "Prihlásenia" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Prihlásenie pomocou zariadenia musí byť nastavené v nastaveniach aplikácie Bitwarden. Potrebujete inú možnosť?" }, + "needAnotherOptionV1": { + "message": "Potrebujete inú možnosť?" + }, "loginWithMasterPassword": { "message": "Prihlásenie pomocou hlavného hesla" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Prihlásenie do Bitwardenu" }, + "authenticationTimeout": { + "message": "Časový limit overenia" + }, + "authenticationSessionTimedOut": { + "message": "Relácia overovania skončila. Znovu spustite proces prihlásenia." + }, "verifyIdentity": { "message": "Overte svoju totožnosť" }, + "whatIsADevice": { + "message": "Čo je zariadenie?" + }, + "aDeviceIs": { + "message": "Zariadenie je jednotlivá inštalácia aplikácie Bitwarden, do ktorej ste sa prihlásili. Preinštalovanie, vymazanie údajov aplikácie alebo vymazanie súborov cookie môže mať za následok, že sa zariadenie objaví viackrát." + }, "logInInitiated": { "message": "Iniciované prihlásenie" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie." }, + "aNotificationWasSentToYourDevice": { + "message": "Do vášho zariadenia bolo odoslané upozornenie" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Uistite sa, že je váš účet odomknutý a fráza odtlačku prsta sa zhoduje s frázou na druhom zariadení" + }, "versionNumber": { "message": "Verzia $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "História hesla" }, + "generatorHistory": { + "message": "História generátora" + }, + "clearGeneratorHistoryTitle": { + "message": "Vymazať históriu generátora" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ak budete pokračovať, všetky položky z histórie generátora budu natrvalo vymazané. Naozaj chcete pokračovať?" + }, "noPasswordsInList": { "message": "Neboli nájdené žiadne heslá." }, + "clearHistory": { + "message": "Vymazať históriu" + }, + "nothingToShow": { + "message": "Nie je čo zobraziť" + }, + "nothingGeneratedRecently": { + "message": "V poslednej dobe ste nič negenerovali" + }, "clear": { "message": "Vyčistiť", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Prosím, opäť sa prihláste." }, + "currentSession": { + "message": "Aktuálna relácia" + }, + "requestPending": { + "message": "Žiadosť čaká na spracovanie" + }, "logBackInOthersToo": { "message": "Prosím odhláste sa. Ak používate iné Bitwarden aplikácie, odhláste sa a opäť sa prihláste aj v nich." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Všetky sedenia odhlásené" }, - "accountIsManagedMessage": { - "message": "Tento účet spravuje $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Tento účet vlastní $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ostáva vám 1 pozvánka." + }, "userUsingTwoStep": { "message": "Tento používateľ používa dvojstupňové overovanie aby si zabezpečil konto." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Zobraziť všetky možnosti prihlásenia" + }, "viewAllLoginOptions": { "message": "Zobraziť všetky možnosti prihlásenia" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Zariadenie" }, + "loginStatus": { + "message": "Stav prihlásenia" + }, + "firstLogin": { + "message": "Prvé prihlásenie" + }, + "trusted": { + "message": "Dôveryhodné" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Aktualizovať prehliadač" }, + "generatingRiskInsights": { + "message": "Generuje sa váš prehľad o rizikách..." + }, "updateBrowserDesc": { "message": "Používate nepodporovaný prehliadač. Webový trezor nemusí úplne fungovať." }, + "freeTrialEndPromptCount": { + "message": "Vaše bezplatné skúšobné obdobie vyprší o $COUNT$ dní.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, vaše bezplatné skúšobné obdobie vyprší o $COUNT$ dní.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, zajtra vyprší vaše bezplatné skúšobné obdobie.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Zajtra vyprší vaše bezplatné skúšobné obdobie." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, dnes vyprší vaše bezplatné skúšobné obdobie.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Dnes vyprší vaše bezplatné skúšobné obdobie." + }, + "clickHereToAddPaymentMethod": { + "message": "Kliknutím tu pridajte platobnú metódu." + }, "joinOrganization": { "message": "Pripojte sa k organizácii" }, @@ -4388,6 +4515,9 @@ "message": "Nepýtať sa pozvaných používateľov na overenie frázy odtlačku. (neodporúča sa)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Po schválení žiadosti budete informovaní" + }, "free": { "message": "Zadarmo", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Prihláste sa prostredníctvom jednotného prihlásenie (SSO) vašej organizácie. Najskôr zadajte identifikátor vašej organizácie." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Ak chcete začať, zadajte identifikátor SSO vašej organizácie" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Pre prihlásenie prostredníctvom vášho poskytovateľa SSO, zadajte SSO identifikátor vašej organizácie. Pri prihlasovaní na novom zariadení môžete byt požiadaní o zadanie tohto SSO identifikátora." + }, "enterpriseSingleSignOn": { "message": "Jednotné prihlásenie pre podniky (SSO)" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Vylúčené, neplatí pre túto akciu." }, + "nonCompliantMembersTitle": { + "message": "Členovia nespĺňajúci pravidlá" + }, + "nonCompliantMembersError": { + "message": "Členovia ktorí nedodržiavajú pravidlo Jednej Organizácie, alebo pravidlo Dvojstupňového Prihlásenia nemôžu byť obnovení dokiaľ nebudú tieto pravidla dodržiavať" + }, "fingerprint": { "message": "Odtlačok prsta" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email Odoslaný" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "Táto ponuka už nie je platná. Pre viac informácií kontaktujte správcu vašej organizácie." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generovať e-mail" }, - "generatorBoundariesHint": { - "message": "Hodnota musí byť medzi $MIN$ a $MAX$", + "spinboxBoundariesHint": { + "message": "Hodnota musí byť medzi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Na vytvorenie silného hesla použite $RECOMMENDED$ znakov alebo viac.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Na vytvorenie silnej prístupovej frázy použite $RECOMMENDED$ slov alebo viac.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Typ používateľského mena" }, @@ -6674,34 +6850,38 @@ "message": "Vyžaduje sa predplatné Prémium" }, "scim": { - "message": "SCIM provisioning", + "message": "SCIM poskytovanie", "description": "The text, 'SCIM', is an acronym and should not be translated." }, "scimDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", + "message": "Automaticky vytvárajte používateľov a skupiny prostredníctvom SCIM s preferovaným poskytovateľom identít", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, + "scimIntegrationDescription": { + "message": "Automaticky vytvárajte používateľov a skupiny prostredníctvom SCIM s preferovaným poskytovateľom identít. Zistite podporované integrácie", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDesc": { - "message": "Enable SCIM", + "message": "Povoliť SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Set up your preferred identity provider by configuring the URL and SCIM API Key", + "message": "Nastavte vášho preferovaného poskytovateľa identít konfiguráciou URL a kľúča API", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimApiKeyHelperText": { - "message": "This API key has access to manage users within your organization. It should be kept secret." + "message": "Tento kľúč API ma prístup k správe používateľov vo vašej organizácii. Mal by byť bezpečne uchovaný." }, "copyScimKey": { - "message": "Copy the SCIM API key to your clipboard", + "message": "Kopírovať SCIM API kľuč do vašej schránky", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKey": { - "message": "Obnovte SCIM API kľúč", + "message": "Obnoviť SCIM API kľúč", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKeyWarning": { - "message": "Are you sure you want to rotate the SCIM API Key? The current key will no longer work for any existing integrations.", + "message": "Naozaj chcete obnoviť kľuč SCIM API? Súčasný kľúč prestane fungovať vo všetkých existujúcich integráciách.", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateKey": { @@ -6712,7 +6892,7 @@ "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "copyScimUrl": { - "message": "Copy the SCIM endpoint URL to your clipboard", + "message": "Kopírovať URL adresu koncového bodu SCIM do vašej schránky", "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimUrl": { @@ -6720,11 +6900,11 @@ "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimApiKeyRotated": { - "message": "SCIM API key successfully rotated", + "message": "Kľuč SCIM API úspešne obnovený", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM settings saved", + "message": "Nastavenia SCIM uložené", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "inputRequired": { @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Iniciované prihlásenie" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Zapamätať si toto zariadenie, pre budúce bezproblémové prihlásenie" + }, "deviceApprovalRequired": { "message": "Vyžaduje sa schválenie zariadenia. Vyberte možnosť schválenia nižšie:" }, + "deviceApprovalRequiredV2": { + "message": "Vyžaduje sa schválenie zariadenia" + }, + "selectAnApprovalOptionBelow": { + "message": "Vyberte možnosť schválenia nižšie" + }, "rememberThisDevice": { "message": "Zapamätať si toto zariadenie" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Schváliť žiadosť" }, + "deviceApproved": { + "message": "Zariadenie schválené" + }, + "deviceRemoved": { + "message": "Zariadenie odstránené" + }, + "removeDevice": { + "message": "Odstrániť zariadenie" + }, + "removeDeviceConfirmation": { + "message": "Ste si istí, že chcete odstrániť toto zariadenie?" + }, "noDeviceRequests": { "message": "Žiadne žiadosti pre zariadenia" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Chýba e-mail používateľa" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "E-mail aktívneho používateľa sa nenašiel. Odhlasuje sa." + }, "deviceTrusted": { "message": "Dôveryhodné zariadenie" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Spravovať správanie zbierky pre organizáciu" }, - "limitCollectionCreationDeletionDesc": { - "message": "Obmedziť vytváranie a vymazávanie zbierky len pre vlastníkov a administrátorov" - }, "limitCollectionCreationDesc": { "message": "Obmedziť vytváranie zbierky len pre vlastníkov a administrátorov" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "pridajte spôsob platby", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Informácie o organizácii" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Použite Bitwarden Secrets Manager SDK v následujúcich programovacích jazykoch pre vytvorenie vašej vlastnej aplikácie." }, - "setUpGithubActions": { - "message": "Nastaviť Github Actions" + "ssoDescStart": { + "message": "Nastaviť", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Nastaviť Kubernetes" + "scimIntegrationDescStart": { + "message": "Nastaviť ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Nastaviť GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) pre automatické vytváranie používateľov a skupiny v Bitwardene pomocou implementačnej príručky pre vášho poskytovateľa identít.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Nastaviť Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "Zobraziť Rust repozitár" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "Zobraziť C# repozitár" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "Zobraziť C++ repozitár" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "Zobraziť JS WebAssembly repozitár" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "Zobraziť Java repozitár" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Zobraziť Python repozitár" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Zobraziť php repozitár" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Zobraziť Ruby repozitár" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Zobraziť Go repozitár" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Vytvoriť novú klientskú organizáciu ktorú môžete spravovať ako Poskytovateľ. Dodatočné sedenia sa prejavia v najbližšom fakturačnom období." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Spravovaný poskytovateľ služieb" + }, + "multiOrganizationEnterprise": { + "message": "Spoločnosť s viacerými organizáciami" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Aktualizované daňové informácie" }, + "billingInvalidTaxIdError": { + "message": "Neplatne číslo pre DPH, ak myslíte že ide o chybu, kontaktujte prosím zákaznícku podporu." + }, + "billingTaxIdTypeInferenceError": { + "message": "Nepodarilo sa nám overiť vaše číslo pre DPH, ak myslíte že ide o chybu, kontaktujte prosím zákaznícku podporu." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Neplatne číslo pre DPH, ak myslíte že ide o chybu, kontaktujte prosím zákaznícku podporu." + }, + "billingPreviewInvoiceError": { + "message": "Pri vytváraní náhľadu faktúry nastala chyba. Prosím skúste to neskor." + }, "unverified": { "message": "Neoverený" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Algoritmus kľúča" + }, + "sshKeyFingerprint": { + "message": "Odtlačok" + }, + "sshKeyPrivateKey": { + "message": "Súkromný kľúč" + }, + "sshKeyPublicKey": { + "message": "Verejný kľúč" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 prémiových účtov" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Vlastný hosting" }, - "verified-domain-single-org-warning": { - "message": "Overenie domény zapne pravidlo jedinej organizácie." + "claim-domain-single-org-warning": { + "message": "Privlastnenie domény zapne pravidlo jedinej organizácie." }, "single-org-revoked-user-warning": { "message": "Členovia, ktorí nedodržiavajú pravidlo, budú odvolaní. Správcovia môžu obnoviť členov po ich odchode zo všetkých ostatných organizácií." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Keď je člen odstránený, jeho účet Bitwarden a individuálne údaje z trezora sa natrvalo odstránia. Údaje zo zbierky zostanú v organizácii. Ak ho chcete znovu pridať, musí si vytvoriť účet a byť znovu zaradený do systému.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Položky vlastnené $NAME$ budú nenávratne odstránené. Položky v zbierke nie sú ovplyvnené.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Položky vlastnené nasledujúcimi členmi budú nenávratne odstránené. Položky v zbierke nie sú ovplyvnené.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Odstránený $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Používateľ bol odstránený z organizácie a všetky súvisiace dáta boli vymazané." + }, + "deletedUserId": { + "message": "Odstránený používateľ $ID$ - vlastnik / správca vymazal používateľský účet", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Používateľ $ID$ opustil organizáciu", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ je pozastavené", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Kontaktujte o pomoc vlastníka organizácie." + }, + "suspendedOwnerOrgMessage": { + "message": "Pre obnovenie prístupu k vašej organizácii pridajte platobnú metódu." + }, + "deleteMembers": { + "message": "Vymazať členov" + }, + "noSelectedMembersApplicable": { + "message": "Táto akcia sa nevzťahuje na žiadneho z vybraných členov." + }, + "deletedSuccessfully": { + "message": "Úspešne odstránené" + }, + "freeFamiliesSponsorship": { + "message": "Odstrániť sponzorovaný bezplatný Bitwarden plán pre Rodiny" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Neumožnite členom používať plán pre rodiny prostredníctvom tejto organizácie." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Platba prostredníctvom bankového účtu je dostupná len pre zákazníkov v Spojených Štátoch. Budete musieť overiť svoj bankový účet. V priebehu nasledujúcich 1-2 pracovných dní vykonáme mikro vklad. Na overenie bankového účtu zadajte kód popisu výpisu z tohto vkladu na fakturačnej stránke organizácie. Neoverenie bankového účtu bude mať za následok neuskutočnenie platby a pozastavenie vášho predplatného." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Na váš bankový účet sme vykonali mikro vklad (môže to trvať 1-2 pracovné dni). Zadajte šesťmiestny kód začínajúci na „SM“, ktorý nájdete v popise vkladu. Neoverenie bankového účtu bude mať za následok neuskutočnenie platby a pozastavenie vášho predplatného." + }, + "descriptorCode": { + "message": "Kód výpisu" + }, + "importantNotice": { + "message": "Dôležité upozornenie" + }, + "setupTwoStepLogin": { + "message": "Nastaviť dvojstupňové prihlásenie" + }, + "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 spoľahlivý prístup k svojmu e-mailu, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "Nie, nemám" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Áno, mám spoľahlivý prístup k svojmu e-mailu" + }, + "turnOnTwoStepLogin": { + "message": "Zapnúť dvojstupňové prihlásenie" + }, + "changeAcctEmail": { + "message": "Zmeniť e-mail účtu" + }, + "removeMembers": { + "message": "Odstrániť členov" + }, + "devices": { + "message": "Zariadenia" + }, + "deviceListDescription": { + "message": "Vaše konto bolo prihlásené do každého z nižšie uvedených zariadení. Ak niektoré zariadenie nepoznáte, teraz ho odstráňte." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Privlastnené domény" + }, + "claimDomain": { + "message": "Privlastniť doménu" + }, + "reclaimDomain": { + "message": "Opätovne privlastniť doménu" + }, + "claimDomainNameInputHint": { + "message": "Príklad: mydomain.com. Pre privlastnenie subdomén musia byť tieto každá zadaná individuálne." + }, + "automaticClaimedDomains": { + "message": "Automaticky privlastnené domény" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden sa pokúsi privlastniť doménu 3 krát počas prvých 72 hodín. Ak sa doménu nepodarilo privlastniť, skontrolujte DNS záznam u svojho hostiteľa a privlastnite manuálne. Doména bude z organizácie odstránená po 7 dňoch ak nie je privlastnená." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ nie je privlastnená. Overte si DNS záznam.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Privlastnená" + }, + "domainStatusUnderVerification": { + "message": "Overuje sa" + }, + "claimedDomainsDesc": { + "message": "Privlastnite si doménu, aby ste vlastnili všetky členské účty, ktorých e-mailová adresa sa zhoduje s doménou. Členovia budú môcť pri prihlasovaní preskočiť identifikátor SSO. Správcovia budú môcť tiež vymazávať členské účty." + }, + "invalidDomainNameClaimMessage": { + "message": "Zadaný neplatný formát. Formát: mydomain.com. Pre privlastnenie subdomén musia byť tieto každá zadaná individuálne." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ privlastnená", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ nie je privlastnená", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Ak odstránite $EMAIL$, sponzorstvo pre tento Rodinný plán nebude možné využiť. Naozaj chcete pokračovať?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Ak odstránite $EMAIL$, sponzorstvo tohto Rodinného plánu sa ukončí a dňa $DATE$ bude uloženým spôsobom platby vykonaná platba $40 + príslušná daň. Nové sponzorstvo si budete môcť uplatniť až po $DATE$. Naozaj chcete pokračovať?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Doména privlastnená" + }, + "organizationNameMaxLength": { + "message": "Meno organizácie nemôže mať viac ako 50 znakov." + }, + "resellerRenewalWarning": { + "message": "Vaše predplatné sa čoskoro obnoví. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Faktúra za vaše predplatné bola vystavená dňa $ISSUED_DATE$. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie predplatného pred $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Faktúra za vaše predplatné nebola uhradená. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index eafa7de2bfe..75ad87494f0 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Zavarovan zapisek" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Prijave" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Prijava z glavnim geslom" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Prijava se je začela" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Na vašo napravo smo poslali obvestilo." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Različica $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Zgodovina gesel" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Ni gesel za prikaz." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Počisti", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Prosimo, ponovno se prijavite." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Prosimo, ponovno se prijavite. Če uporabljate druge Bitwarden aplikacije, se odjavite in ponovno prijavite tudi tam." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Vse seje prekinjene" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "Prikaži vse možnosti prijave" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Vrsta uporabniškega imena" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" + }, + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "rustSDKRepo": { - "message": "View Rust repository" + "eventManagement": { + "message": "Event management" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "deviceManagement": { + "message": "Device management" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "javaSDKRepo": { - "message": "View Java repository" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 9a568d445e2..0f3853331c2 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Критичне апликације" }, + "accessIntelligence": { + "message": "Приступи интелигенцији" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Увид у ризик" }, "passwordRisk": { "message": "Ризик од лозинке" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Подаци су последњи пут ажурирани: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Обавештени чланови" }, + "revokeMembers": { + "message": "Уклони чланове" + }, + "restoreMembers": { + "message": "Врати чланове" + }, + "cannotRestoreAccessError": { + "message": "Није могуће повратити приступ организацији" + }, "allApplicationsWithCount": { "message": "Све апликације ($COUNT$)", "placeholders": { @@ -66,10 +78,10 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Док корисници чувају пријаве, апликације се појављују овде, приказујући све ризичне лозинке. Означите критичне апликације и обавестите кориснике да ажурирају лозинке." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Нисте означили ниједну апликацију као критичну" }, "noCriticalAppsDescription": { "message": "Изаберите своје најкритичније апликације да бисте открили ризичне лозинке и обавестите кориснике да промене те лозинке." @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Сигурносна белешка" }, + "typeSshKey": { + "message": "SSH кључ" + }, "typeLoginPlural": { "message": "Пријаве" }, @@ -635,7 +650,7 @@ "message": "Види ставку" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Нови $TYPE$", "placeholders": { "type": { "content": "$1", @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Пријава помоћу уређаја мора бити подешена у подешавањима Bitwarden апликације. Потребна је друга опција?" }, + "needAnotherOptionV1": { + "message": "Треба Вам друга опције?" + }, "loginWithMasterPassword": { "message": "Пријавите се са главном лозинком" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Пријавите се на Bitwarden" }, + "authenticationTimeout": { + "message": "Истекло је време аутентификације" + }, + "authenticationSessionTimedOut": { + "message": "Истекло је време сесије за аутентификацију. Молим вас покрените процес пријаве поново." + }, "verifyIdentity": { "message": "Потврдите идентитет" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Пријава је покренута" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." }, + "aNotificationWasSentToYourDevice": { + "message": "Обавештење је послато на ваш уређај" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" + }, "versionNumber": { "message": "Верзија $VERSION_NUMBER$", "placeholders": { @@ -1441,7 +1477,7 @@ "message": "Желите ли заиста да наставите?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Изаберите фасциклу коју желите да додате $COUNT$ изабраним ставкама.", "placeholders": { "count": { "content": "$1", @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Историја Лозинке" }, + "generatorHistory": { + "message": "Генератор историје" + }, + "clearGeneratorHistoryTitle": { + "message": "Испразнити генератор историје" + }, + "cleargGeneratorHistoryDescription": { + "message": "Ако наставите, сви уноси ће бити трајно избрисани из генератора историје. Да ли сте сигурни да желите да наставите?" + }, "noPasswordsInList": { "message": "Нама лозинке у листи." }, + "clearHistory": { + "message": "Обриши историју" + }, + "nothingToShow": { + "message": "Ништа да се покаже" + }, + "nothingGeneratedRecently": { + "message": "Недавно нисте ништа генерисали" + }, "clear": { "message": "Очисти", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Молимо да се поново пријавите." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Молимо вас да се поново пријавите. Ако користите друге Bitwarden апликације, одјавите се и вратите се и на њих." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Одузето овлашћење свих сесија" }, - "accountIsManagedMessage": { - "message": "Овим налогом управља $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Овај налого припада $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1818,7 +1878,7 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "нова пријава", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Овај корисник користи пријаву у два корака за заштиту свог налога." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Погледајте сав извештај у опције" + }, "viewAllLoginOptions": { "message": "Погледајте сав извештај у опције" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Уређај" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Креирај налог на" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Ажурирајте Претраживач" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Користите неподржани веб прегледач. Веб сеф можда неће правилно функционисати." }, + "freeTrialEndPromptCount": { + "message": "Ваша проба се завршава за $COUNT$ дана.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, Ваша проба са завршава за $COUNT$ дана.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, Ваша проба са завршава сутра.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Ваша бесплатна пробна се завршава сутра." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, Ваша проба са завршава данас.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Ваша бесплатна пробна се завршава данас." + }, + "clickHereToAddPaymentMethod": { + "message": "Кликните овде да додате начин плаћања." + }, "joinOrganization": { "message": "Придружи Организацију" }, @@ -4388,6 +4515,9 @@ "message": "Не питај више за проверу Сигурносне Фразе Сефа", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Бићете обавештени када захтев буде одобрен" + }, "free": { "message": "Бесплатно", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Пријавите се помоћу портала за јединствену пријаву ваше организације. Унесите идентификатор организације да бисте започели." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise Једна Пријава" }, @@ -4686,7 +4822,7 @@ "message": "Ограничите корисницима могућност придруживања било којој другој организацији." }, "singleOrgPolicyDesc": { - "message": "Ограничите чланове да се придруже другим организацијама. Ова смерница је неопходна за организације које су омогућиле верификацију домена." + "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." }, "singleOrgBlockCreateMessage": { "message": "Ваша тренутна организација има смернице које не дозвољавају да се придружите више организација. Молимо контактирајте администраторе своје организације или се пријавите са другим Bitwarden налога." @@ -4695,7 +4831,7 @@ "message": "Чланови организације који нису власници или администратори и који су већ чланови друге организације биће уклоњени из ваше организације." }, "singleOrgPolicyMemberWarning": { - "message": "Чланови који не испуњавају услове биће стављени у статус опозива док не напусте све друге организације. Администратори су изузети и могу да поврате чланове када се испоштује усаглашеност." + "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." }, "requireSso": { "message": "Аутентификација једнократном пријавом" @@ -5268,7 +5404,7 @@ "message": "погледајте и изаберите чланове којима желите да дате приступ Менаџеру тајни." }, "openYourOrganizations": { - "message": "Open your organization's" + "message": "Отворите вашу организацију" }, "usingTheMenuSelect": { "message": "Помоћу менија, изаберите" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Искључено, није применљиво за ову акцију." }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Отисак прста" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Корисницима за управљање такође мора бити додељена дозвола за опоравак налога за управљање" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Е-пошта је послата" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Спонзорство уклоњено" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Потребно ако Entity ID није УРЛ." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Опциона подешавања" }, @@ -6411,10 +6567,10 @@ "message": "Генериши име" }, "generateEmail": { - "message": "Generate email" + "message": "Генеришите имејл" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип имена" }, @@ -6451,23 +6627,23 @@ "message": "Случајна реч" }, "usernameGenerator": { - "message": "Username generator" + "message": "Генератор корисничког имена" }, "useThisPassword": { - "message": "Use this password" + "message": "Употреби ову лозинку" }, "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'" }, "service": { @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Упали SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Пријава је покренута" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, + "deviceApprovalRequiredV2": { + "message": "Потребно је одобрење уређаја" + }, + "selectAnApprovalOptionBelow": { + "message": "Изаберите опцију одобрења у наставку" + }, "rememberThisDevice": { "message": "Запамти овај уређај" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Одобри захтев" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Нема захтева уређаја" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Недостаје имејл корисника" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Уређај поуздан" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Управљајте понашањем збирке за организацију" }, - "limitCollectionCreationDeletionDesc": { - "message": "Ограничите креирање и брисање збирке на власнике и администраторе" - }, "limitCollectionCreationDesc": { "message": "Ограничите креирање збирке на власнике и администраторе" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "додајте начин плаћања", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Информације о организацији" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Употребите Bitwarden Secrets Manager SDK на следећим програмским језицима да направите сопствене апликације." }, - "setUpGithubActions": { - "message": "Подесити акције GitHub-а" + "ssoDescStart": { + "message": "Подеси", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Подесите Kubernetes" + "scimIntegrationDescStart": { + "message": "Подеси ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Подесити GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Подесити Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "Преглед Rust спремишта" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "Преглед C# спремишта" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "Преглед C++ спремишта" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "Преглед JS WebAssembly спремишта" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "Преглед Java спремишта" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Преглед Python спремишта" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Преглед php спремишта" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Преглед Ruby спремишта" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Преглед Go спремишта" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Креирајте нову клијентску организацију којом ћете управљати као добављач. Додатна места ће се одразити у следећем обрачунском циклусу." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Управљени провајдери сервиса" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Седиста организације" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Ажуриране пореске информације" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Непроверено" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "ГБ додатног простора за складиштење" }, + "sshKeyAlgorithm": { + "message": "Алгоритам кључа" + }, + "sshKeyFingerprint": { + "message": "Отисак прста" + }, + "sshKeyPrivateKey": { + "message": "Приватни кључ" + }, + "sshKeyPublicKey": { + "message": "Јавни кључ" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-бита" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-бита" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-бита" + }, "premiumAccounts": { "message": "6 премијум налога" }, @@ -9481,7 +9779,7 @@ "message": "Сакриј бројање слова" }, "editAccess": { - "message": "Edit access" + "message": "Уредити приступ" }, "textHelpText": { "message": "Користите текстуална поља за податке као што су безбедносна питања" @@ -9549,14 +9847,14 @@ "selfHostingTitleProper": { "message": "Селф-Хостинг" }, - "verified-domain-single-org-warning": { - "message": "Верификација домена ће укључити политику једне организације." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { - "message": "Чланови који не испуњавају услове биће опозвани. Администратори могу вратити чланове када напусте све друге организације." + "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Обриши $NAME$", "placeholders": { "name": { "content": "$1", @@ -9565,12 +9863,22 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ је обрисан", "placeholders": { "name": { "content": "$1", @@ -9579,6 +9887,254 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "Корисник је уклоњен из организације и сви повезани кориснички подаци су избрисани." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "$ID$ је напустио организацију", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ је суспендована", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Обратите се власнику ваше организације за помоћ." + }, + "suspendedOwnerOrgMessage": { + "message": "Да бисте поново добили приступ својој организацији, додајте начин плаћања." + }, + "deleteMembers": { + "message": "Избрисати чланове" + }, + "noSelectedMembersApplicable": { + "message": "Ова акција није применљива на било који од одабраних чланова." + }, + "deletedSuccessfully": { + "message": "Успешно обрисано" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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": "Променити имејл налога" + }, + "removeMembers": { + "message": "Уклони чланове" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Под провером" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 7c46cf4275f..06572d08fea 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Zaštićena beleška" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Istorija lozinki" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Očisti", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index ecf9a0a5e4f..f9737643631 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Kritiska applikationer" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Riskinsikter" }, "passwordRisk": { "message": "Lösenordsrisk" }, - "discoverAtRiskPasswords": { - "message": "Upptäck sårbara lösenord och meddela användarna att de ska byta lösenord." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "Alla applikationer ($COUNT$)", "placeholders": { @@ -36,7 +48,7 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Skapa nytt inloggningsobjekt" }, "criticalApplicationsWithCount": { "message": "Kritiska applikationer ($COUNT$)", @@ -142,7 +154,7 @@ "message": "Nytt lösenord" }, "passphrase": { - "message": "Lösenordsfras" + "message": "Lösenfras" }, "notes": { "message": "Anteckningar" @@ -460,7 +472,7 @@ "message": "Generera lösenfras" }, "checkPassword": { - "message": "Kontrollera om lösenordet har avslöjats." + "message": "Kontrollera om ditt lösenord har äventyrats." }, "passwordExposed": { "message": "Detta lösenord har avslöjats $VALUE$ gång(er) i dataintrång. Du bör ändra det.", @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Säker anteckning" }, + "typeSshKey": { + "message": "SSH-nyckel" + }, "typeLoginPlural": { "message": "Inloggningar" }, @@ -762,7 +777,7 @@ "message": "Kopiera telefon" }, "copyEmail": { - "message": "Copy email" + "message": "Kopiera e-postadress" }, "copyCompany": { "message": "Kopiera företag" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "\"Logga in med enhet\" måste ställas in i inställningarna i Bitwardens app. Behöver du ett annat alternativ?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Logga in med huvudlösenord" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Logga in på Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Inloggning påbörjad" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "En avisering har skickats till din enhet." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1336,10 +1372,10 @@ "message": "Använd en annan metod för tvåstegsverifiering" }, "insertYubiKey": { - "message": "Anslut din YubiKey till datorns USB-port och tryck sedan på dess knapp." + "message": "Sätt in din YubiKey i din dators USB-port och tryck på dess knapp." }, "insertU2f": { - "message": "Anslut din säkerhetsnyckel till datorns USB-port. Om den har en knapp, tryck på den." + "message": "Sätt in din säkerhetsnyckel i din dators USB-port. Om den har en knapp, tryck på den." }, "loginUnavailable": { "message": "Inloggning ej tillgänglig" @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Lösenordshistorik" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Det finns inga lösenord att visa." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Rensa", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Vänligen logga in igen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vänligen logga in igen. Om du använder andra Bitwarden-applikationer, logga ut och in igen i dem också." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Alla sessioner avauktoriserades" }, - "accountIsManagedMessage": { - "message": "Detta konto hanteras av $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1822,7 +1882,7 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { - "message": " istället.", + "message": " i stället.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." }, "onboardingImportDataDetailsPartTwoWithOrgs": { @@ -2120,7 +2180,7 @@ "message": "Lägg till en ny YubiKey till ditt konto" }, "twoFactorYubikeyPlugIn": { - "message": "Anslut din YubiKey till datorns USB-port." + "message": "Sätt in YubiKey i din dators USB-port." }, "twoFactorYubikeySelectKey": { "message": "Välj det första tomma YubiKey-inmatningsfältet nedan." @@ -2225,7 +2285,7 @@ "message": "Ge säkerhetsnyckeln ett namn för att kunna identifiera den." }, "twoFactorU2fPlugInReadKey": { - "message": "Anslut säkerhetsnyckeln till din dators USB-port och klicka på knappen \"Läs nyckel\"." + "message": "Sätt in säkerhetsnyckeln i din dators USB-port, och klicka på knappen \"Läs nyckel\"." }, "twoFactorU2fTouchButton": { "message": "Om säkerhetsnyckeln har en knapp, tryck på den." @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Denna användare använder tvåstegsverifiering för att skydda sitt konto." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Visa alla inloggningsalternativ" + }, "viewAllLoginOptions": { "message": "Visa alla inloggningsalternativ" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Skapa konto på" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Uppdatera webbläsare" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "Du använder en webbläsare som inte stöds. Webbvalvet kanske inte fungerar som det ska." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Gå med i organisation" }, @@ -4216,13 +4343,13 @@ "message": "Du har inte markerat något." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Få råd, tillkännagivanden och forskningsmöjligheter från Bitwarden i din inkorg." }, "unsubscribe": { "message": "Unsubscribe" }, "atAnyTime": { - "message": "at any time." + "message": "när som helst." }, "byContinuingYouAgreeToThe": { "message": "By continuing, you agree to the" @@ -4388,6 +4515,9 @@ "message": "Fråga aldrig om att verifiera fingeravtrycksfraser för inbjudna användare (rekommenderas inte)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Gratis", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Logga in genom organisationens inloggningsportal. Ange organisationens identifierare för att börja." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5247,7 +5383,7 @@ "message": "Bitwarden Secrets Manager" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "Fler produkter från Bitwarden" }, "requestAccessToSecretsManager": { "message": "Request access to Secrets Manager" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Exkluderad, inte tillämplig för denna åtgärd" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingeravtryck" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -5559,7 +5715,7 @@ "message": "Klienter" }, "client": { - "message": "Client", + "message": "Klient", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { @@ -5826,7 +5982,7 @@ "message": "Authority" }, "clientId": { - "message": "Client ID" + "message": "Klient-ID" }, "clientSecret": { "message": "Klienthemlighet" @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6285,7 +6438,7 @@ "message": "1 fält ovan kräver din uppmärksamhet." }, "fieldRequiredError": { - "message": "$FIELDNAME$ is required.", + "message": "$FIELDNAME$ är obligatoriskt.", "placeholders": { "fieldname": { "content": "$1", @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Värde måste vara mellan $MIN$ och $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Använd minst $RECOMMENDED$ tecken för att generera ett starkt lösenord.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Använd minst $RECOMMENDED$ ord för att generera en stark lösenfras.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Användarnamnstyp" }, @@ -6454,10 +6630,10 @@ "message": "Username generator" }, "useThisPassword": { - "message": "Use this password" + "message": "Använd detta lösenord" }, "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." @@ -6559,7 +6735,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Webbplats: $WEBSITE$. Genererad av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Aktivera SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -6902,7 +7082,7 @@ "message": "Rensa alla" }, "toggleCharacterCount": { - "message": "Toggle character count", + "message": "Visa/dölj teckenantal", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "passwordCharacterCount": { @@ -7372,10 +7552,10 @@ "message": "Ingen samlingar lades till" }, "noMembersAdded": { - "message": "No members added" + "message": "Inga medlemmar tillagda" }, "noGroupsAdded": { - "message": "No groups added" + "message": "Inga grupper tillagda" }, "group": { "message": "Grupp" @@ -7953,14 +8133,23 @@ "description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects." }, "updateKdfSettings": { - "message": "Update KDF settings" + "message": "Uppdatera KDF-inställningar" }, "loginInitiated": { "message": "Inloggning påbörjad" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Kom ihåg denna enhet" }, @@ -8041,7 +8230,7 @@ "description": "Default title for the user verification dialog." }, "recoverAccount": { - "message": "Recover account" + "message": "Återställ konto" }, "updatedTempPassword": { "message": "User updated a password issued through account recovery." @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Inga enhetsförfrågningar" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "lägg till en betalningsmetod", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Ställ in GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Ställ in Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -8973,7 +9229,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tillbaka till $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9030,7 +9292,7 @@ "message": "Low KDF iterations. Increase your iterations to improve the security of your account." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "Ändra KDF-inställningar" }, "secureYourInfrastructure": { "message": "Säkra din infrastruktur" @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Nyckelalgoritm" + }, + "sshKeyFingerprint": { + "message": "Fingeravtryck" + }, + "sshKeyPrivateKey": { + "message": "Privat nyckel" + }, + "sshKeyPublicKey": { + "message": "Offentlig nyckel" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9499,7 +9797,7 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkludera versaler", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9507,7 +9805,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkludera gemener", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9515,7 +9813,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inkludera siffror", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Radera medlemmar" + }, + "noSelectedMembersApplicable": { + "message": "Denna åtgärd är inte tillämplig på någon av de valda medlemmarna." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 57cc9a404e1..e6cce3405d5 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Logins" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Log in with master password" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 8710f029f4e..5f5818581f6 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Secure note" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "เข้าสู่ระบบ" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "เข้าสู่ระบบด้วยรหัสผ่านหลัก" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "All sessions deauthorized" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Free", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 45bbc83fa35..97c0d00dec2 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -3,19 +3,22 @@ "message": "Tüm uygulamalar" }, "criticalApplications": { - "message": "Critical applications" + "message": "Kritik uygulamalar" + }, + "accessIntelligence": { + "message": "Access Intelligence" }, "riskInsights": { - "message": "Risk Insights" + "message": "Risk İçgörüleri" }, "passwordRisk": { "message": "Parola Riski" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Uygulamalar genelinde risk altındaki parolaları (zayıf, açık veya yeniden kullanılan) gözden geçirin. Kullanıcılarınız için risk altındaki parolalara yönelik güvenlik eylemlerine öncelik vermek üzere en kritik uygulamalarınızı seçin." }, "dataLastUpdated": { - "message": "Son veri güncellemesi: $DATE$", + "message": "Veri son güncellenme tarihi: $DATE$", "placeholders": { "date": { "content": "$1", @@ -24,7 +27,16 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Bildirilen üyeler" + }, + "revokeMembers": { + "message": "Üyeleri iptal et" + }, + "restoreMembers": { + "message": "Üyeleri geri yükle" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" }, "allApplicationsWithCount": { "message": "Tüm uygulamalar ($COUNT$)", @@ -48,7 +60,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Bildirilen üyeler ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -81,7 +93,7 @@ "message": "Mark app as critical" }, "appsMarkedAsCritical": { - "message": "Kritik olarak işaretlenmiş uygulamalar" + "message": "Apps marked as critical" }, "application": { "message": "Uygulama" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Güvenli not" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Hesaplar" }, @@ -635,7 +650,7 @@ "message": "Kaydı göster" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Yeni $TYPE$", "placeholders": { "type": { "content": "$1", @@ -644,7 +659,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "$TYPE$'ı düzenle", "placeholders": { "type": { "content": "$1", @@ -722,7 +737,7 @@ "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Parola kopyalandı" + "message": "Password copied" }, "copyUsername": { "message": "Kullanıcı adını kopyala", @@ -931,7 +946,7 @@ "message": "Erişim seviyesi" }, "accessing": { - "message": "Accessing" + "message": "Erişim" }, "loggedOut": { "message": "Çıkış yapıldı" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Cihazla girişi Bitwarden mobil uygulamasının ayarlarından etkinleştirmelisiniz. Başka bir seçeneğe mi ihtiyacınız var?" }, + "needAnotherOptionV1": { + "message": "Başka bir seçeneğe mi ihtiyacınız var?" + }, "loginWithMasterPassword": { "message": "Ana parola ile giriş yap" }, @@ -991,13 +1009,13 @@ "message": "Başka bir giriş yöntemi kullan" }, "logInWithPasskey": { - "message": "Geçiş anahtarıyla giriş yap" + "message": "Log in with passkey" }, "useSingleSignOn": { - "message": "Çoklu oturum açma kullan" + "message": "Use single sign-on" }, "welcomeBack": { - "message": "Tekrar hoş geldiniz" + "message": "Welcome back" }, "invalidPasskeyPleaseTryAgain": { "message": "Geçersiz geçiş anahtarı. Lütfen tekrar deneyin." @@ -1081,7 +1099,7 @@ "message": "Hesap aç" }, "newToBitwarden": { - "message": "Bitwarden'da yeni misiniz?" + "message": "New to Bitwarden?" }, "setAStrongPassword": { "message": "Güçlü bir parola belirleyin" @@ -1099,11 +1117,23 @@ "message": "Giriş yap" }, "logInToBitwarden": { - "message": "Bitwarden'a giriş yapın" + "message": "Log in to Bitwarden" + }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." }, "verifyIdentity": { "message": "Kimliğinizi doğrulayın" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Giriş başlatıldı" }, @@ -1270,7 +1300,7 @@ "message": "Bu koleksiyondaki tüm kayıtları görmek için izniniz yok." }, "youDoNotHavePermissions": { - "message": "Bu koleksiyona erişme izniniz yok" + "message": "You do not have permissions to this collection" }, "noCollectionsInList": { "message": "Listelenecek koleksiyon yok." @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Cihazınıza bir bildirim gönderildi." }, + "aNotificationWasSentToYourDevice": { + "message": "Cihazınıza bir bildirim gönderildi" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Lütfen hesabınızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun" + }, "versionNumber": { "message": "Sürüm $VERSION_NUMBER$", "placeholders": { @@ -1476,10 +1512,10 @@ "message": "UUID'yi kopyala" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Erişim Anahtarı Yenileme Hatası" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Yenileme veya API anahtarı bulunamadı. Lütfen çıkış yapıp tekrar giriş yapmayı deneyin." }, "warning": { "message": "Uyarı" @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Parola geçmişi" }, + "generatorHistory": { + "message": "Üreteç geçmişi" + }, + "clearGeneratorHistoryTitle": { + "message": "Üreteç geçmişini temizle" + }, + "cleargGeneratorHistoryDescription": { + "message": "Devam ederseniz üreteç geçmişindeki tüm kayıtlar kalıcı olarak silinecektir. Devam etmek istediğinizden emin misiniz?" + }, "noPasswordsInList": { "message": "Listelenecek parola yok." }, + "clearHistory": { + "message": "Geçmişi temizle" + }, + "nothingToShow": { + "message": "Gösterilecek bir şey yok" + }, + "nothingGeneratedRecently": { + "message": "Yakın zamanda herhangi bir şey üretmediniz" + }, "clear": { "message": "Temizle", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Lütfen yeniden giriş yapın." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Lütfen yeniden oturum açın. Diğer Bitwarden uygulamalarını kullanıyorsanız onlarda da oturumunuzu kapatıp yeniden açın." }, @@ -1738,7 +1798,7 @@ "message": "Dikkatli olun, bu işlemleri geri alamazsınız!" }, "dangerZoneDescSingular": { - "message": "Dikkatli olun, bu işlemi geri alamazsınız!" + "message": "Careful, this action is not reversible!" }, "deauthorizeSessions": { "message": "Oturumları kapat" @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Tüm oturumlar kapatıldı" }, - "accountIsManagedMessage": { - "message": "Bu hesap $ORGANIZATIONNAME$ tarafından yönetilmektedir", + "accountIsOwnedMessage": { + "message": "Bu hesap $ORGANIZATIONNAME$ kuruluşuna aittir", "placeholders": { "organizationName": { "content": "$1", @@ -1798,7 +1858,7 @@ "message": "Hesabınız kapatıldı ve ilişkili tüm veriler silindi." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "Kuruluşunuzu silmek kalıcı bir eylemdir ve geri alınamaz." }, "myAccount": { "message": "Hesabım" @@ -2087,19 +2147,19 @@ } }, "continueToExternalUrlDesc": { - "message": "You are leaving Bitwarden and launching an external website in a new window." + "message": "Bitwarden'dan çıkıyorsunuz ve harici bir siteye yeni pencerede giriş yapıyorsunuz." }, "twoStepContinueToBitwardenUrlTitle": { "message": "bitwarden.com'a gitmek ister misiniz?" }, "twoStepContinueToBitwardenUrlDesc": { - "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 Authenticator size kimlik doğrulayıcı anahtarınızı saklama ve 2 aşamalı doğrulama akışları için TOTP anahtarı üretme imkanı sağlar. Daha fazlası için bitwarden.com sitesini ziyaret edin." }, "twoStepAuthenticatorScanCodeV2": { "message": "Aşağıdaki QR kodunu kimlik doğrulama uygulamanıza okutun veya anahtarı girin." }, "twoStepAuthenticatorQRCanvasError": { - "message": "Could not load QR code. Try again or use the key below." + "message": "QR kodu yüklenemiyor. Yeniden deneyin ya da aşağıdaki kodu kullanın." }, "key": { "message": "Anahtar" @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 davetiyeniz kaldı." + }, "userUsingTwoStep": { "message": "Bu kullanıcı hesabını korumak için iki aşamalı giriş kullanıyor." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Tüm giriş seçeneklerini gör" + }, "viewAllLoginOptions": { "message": "Tüm giriş seçeneklerini gör" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Hesap oluşturuluyor:" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Tarayıcıyı güncelle" }, + "generatingRiskInsights": { + "message": "Risk içgörüleriniz oluşturuluyor..." + }, "updateBrowserDesc": { "message": "Desteklenmeyen bir web tarayıcısı kullanıyorsunuz. Web kasası düzgün çalışmayabilir." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Kuruluşa katıl" }, @@ -4388,6 +4515,9 @@ "message": "Davet edilen kullanıcıların parmak izi ifedelerini doğrulamalarını isteme (önerilmez)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "İsteğiniz onaylanınca size haber vereceğiz" + }, "free": { "message": "Ücretsiz", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Kuruluşunuzun çoklu oturum açma portalını kullanarak giriş yapabilirsiniz. Başlamak için lütfen kuruluşunuzun SSO tanımlayıcısını girin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Kurumsal çoklu oturum açma" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "İstisna. Bu eylem için geçerli değildir" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Parmak izi" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "E-posta gönderildi" }, - "revokeSponsorshipConfirmation": { - "message": "Bu hesap kaldırıldıktan sonra, fatura döneminin sonunda Aile planı sponsorluğu sona erecektir. Mevcut olanın süresi dolana kadar yeni bir sponsorluk teklifinden yararlanamazsınız. Devam etmek istediğine emin misin?" - }, "removeSponsorshipSuccess": { "message": "Sponsorluk kaldırdıldı" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Varlık Kimliği bir URL değilse gereklidir." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "İsteğe bağlı özelleştirmeler" }, @@ -6411,10 +6567,10 @@ "message": "Kullanıcı adı oluştur" }, "generateEmail": { - "message": "E-posta oluştur" + "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır", + "spinboxBoundariesHint": { + "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Kullanıcı adı türü" }, @@ -6533,11 +6709,11 @@ "message": "Harici bir yönlendirme servisiyle e-posta maskesi oluştur." }, "forwarderDomainName": { - "message": "E-posta alan adı", + "message": "Email domain", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Seçtiğiniz servisin desteklediği bir alan adı seçin", + "message": "Choose a domain that is supported by the selected service", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "SCIM'i etkinleştir", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Giriş başlatıldı" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Sonraki girişleri kolaylaştırmak için bu cihazı hatırla" + }, "deviceApprovalRequired": { "message": "Cihaz onayı gerekiyor. Lütfen onay yönteminizi seçin:" }, + "deviceApprovalRequiredV2": { + "message": "Cihazı onaylamanız gerekiyor" + }, + "selectAnApprovalOptionBelow": { + "message": "Aşağıdan bir onay yöntemi seçin" + }, "rememberThisDevice": { "message": "Bu cihazı hatırla" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "İsteği onayla" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Cihaz isteği yok" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Kullanıcının e-postası eksik" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Aktif kullanıcı e-postası bulunamadı. Çıkış yapılıyor." + }, "deviceTrusted": { "message": "Cihaza güvenildi" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Kuruluş için toplama davranışını yönetin" }, - "limitCollectionCreationDeletionDesc": { - "message": "Koleksiyon oluşturma ve silme işlemlerini sahipler ve yöneticilerle sınırlandırın" - }, "limitCollectionCreationDesc": { "message": "Koleksiyon oluşturmayı sahipler ve yöneticilerle sınırlandırın" }, @@ -8340,7 +8541,7 @@ "message": "Sunucu URL'si" }, "selfHostBaseUrl": { - "message": "Kendi kendine barındırılan sunucu URL'si", + "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, "aliasDomain": { @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "bir ödeme yöntemi ekleyin", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Kuruluş bilgileri" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegration": { + "message": "SCIM" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "cSharpSDKRepo": { - "message": "View C# repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagement": { + "message": "Event management" }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." + }, + "deviceManagement": { + "message": "Device management" + }, + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "javaSDKRepo": { - "message": "View Java repository" + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Doğrulanmadı" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium hesap" }, @@ -9540,17 +9838,17 @@ "message": "Bu dosyayı kalıcı olarak silmek istediğinizden emin misiniz?" }, "manageSubscriptionFromThe": { - "message": "Aboneliğinizi yönetin:", + "message": "Manage subscription from the", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." }, "selfHostingTitleProper": { - "message": "Kendi Kendinize Barındırma" + "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Üyeleri sil" + }, + "noSelectedMembersApplicable": { + "message": "Bu eylem seçilen üyelerden hiçbirine uygulanamıyor." + }, + "deletedSuccessfully": { + "message": "Başarıyla silindi" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "importantNotice": { + "message": "Önemli uyarı" + }, + "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": "Daha sonra hatırlat" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Hesabınıza aşağıdaki cihazlardan giriş yapıldı." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 624fb132a81..d8cd9270763 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Критичні програми" }, + "accessIntelligence": { + "message": "Управління доступом" + }, "riskInsights": { - "message": "Risk Insights" + "message": "Інформація щодо ризику" }, "passwordRisk": { "message": "Ризиковані паролі" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Переглядайте ризиковані паролі в різних програмах (слабкі, викриті, або повторно використані). Виберіть найбільш критичні програми, щоб визначити пріоритети дій щодо безпеки для користувачів, які використовують ризиковані паролі." }, "dataLastUpdated": { "message": "Дані востаннє оновлено: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Сповіщення учасників" }, + "revokeMembers": { + "message": "Відкликати учасників" + }, + "restoreMembers": { + "message": "Відновити учасників" + }, + "cannotRestoreAccessError": { + "message": "Не вдається відновити доступ до організації" + }, "allApplicationsWithCount": { "message": "Всі програми ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Захищена нотатка" }, + "typeSshKey": { + "message": "Ключ SSH" + }, "typeLoginPlural": { "message": "Записи" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Потрібно увімкнути схвалення запитів на вхід у налаштуваннях програми Bitwarden. Потрібен інший варіант?" }, + "needAnotherOptionV1": { + "message": "Потрібен інший варіант?" + }, "loginWithMasterPassword": { "message": "Увійти з головним паролем" }, @@ -994,7 +1012,7 @@ "message": "Увійти з ключем доступу" }, "useSingleSignOn": { - "message": "Використовувати єдиний вхід" + "message": "Використати єдиний вхід" }, "welcomeBack": { "message": "З поверненням" @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Увійти в Bitwarden" }, + "authenticationTimeout": { + "message": "Час очікування автентифікації" + }, + "authenticationSessionTimedOut": { + "message": "Час очікування сеансу автентифікації завершився. Перезапустіть процес входу в систему." + }, "verifyIdentity": { "message": "Підтвердьте свою особу" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Ініційовано вхід" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." }, + "aNotificationWasSentToYourDevice": { + "message": "Сповіщення надіслано на ваш пристрій" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Переконайтеся, що ваш обліковий запис розблоковано і фраза відбитка на іншому пристрої збігається" + }, "versionNumber": { "message": "Версія $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Історія паролів" }, + "generatorHistory": { + "message": "Історія генератора" + }, + "clearGeneratorHistoryTitle": { + "message": "Очистити історію генератора" + }, + "cleargGeneratorHistoryDescription": { + "message": "Якщо ви продовжите, усі записи будуть остаточно видалені з історії генератора. Справді продовжити?" + }, "noPasswordsInList": { "message": "Немає паролів." }, + "clearHistory": { + "message": "Очистити історію" + }, + "nothingToShow": { + "message": "Немає даних для показу" + }, + "nothingGeneratedRecently": { + "message": "Ви нічого не генерували останнім часом" + }, "clear": { "message": "Стерти", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Повторно виконайте вхід." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Будь ласка, повторно виконайте вхід. Якщо ви користуєтесь іншими програмами Bitwarden, також вийдіть із них, і знову увійдіть." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Усі сеанси завершено" }, - "accountIsManagedMessage": { - "message": "Цим обліковим записом керує $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "Цим обліковим записом володіє $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Цей користувач використовує двоетапну перевірку для захисту свого облікового запису." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "Переглянути всі варіанти входу" + }, "viewAllLoginOptions": { "message": "Переглянути всі варіанти входу" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Пристрій" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Створення облікового запису" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Оновити браузер" }, + "generatingRiskInsights": { + "message": "Генерується інформація щодо ризиків..." + }, "updateBrowserDesc": { "message": "Ви використовуєте непідтримуваний браузер. Вебсховище може працювати неправильно." }, + "freeTrialEndPromptCount": { + "message": "Ваш безплатний пробний період завершується через $COUNT$ днів.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, ваш безплатний пробний період завершується через $COUNT$ днів.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, ваш безплатний пробний період завершується завтра.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Ваш безплатний пробний період завершується завтра." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, ваш безплатний пробний період завершується сьогодні.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Ваш безплатний пробний період завершується сьогодні." + }, + "clickHereToAddPaymentMethod": { + "message": "Натисніть тут, щоб додати спосіб оплати." + }, "joinOrganization": { "message": "Приєднатися до організації" }, @@ -4388,6 +4515,9 @@ "message": "Ніколи не вимагати перевірки фрази відбитка у запрошених користувачів (не рекомендовано)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "Після схвалення запиту ви отримаєте сповіщення" + }, "free": { "message": "Безплатно", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Виконуйте вхід з використанням порталу єдиного входу вашої організації. Щоб почати, введіть SSO-ідентифікатор вашої організації." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Введіть ідентифікатор SSO вашої організації, щоб почати" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "Щоб увійти зі своїм провайдером SSO, спершу введіть ідентифікатор SSO організації. Можливо, вам доведеться ввести цей ідентифікатор SSO під час входу в систему з нового пристрою." + }, "enterpriseSingleSignOn": { "message": "Єдиний корпоративний вхід (SSO)" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Виключено, не застосовується для цієї дії" }, + "nonCompliantMembersTitle": { + "message": "Учасники, які не відповідають вимогам" + }, + "nonCompliantMembersError": { + "message": "Учасників, які не відповідають вимогам єдиної організації або політиці двоетапної перевірки, не можна відновити доки вони не дотримуватимуться вимог політики" + }, "fingerprint": { "message": "Цифровий відбиток" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Разом із дозволом на керування відновленням облікового запису також необхідно надати дозвіл на керування користувачами" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Лист надіслано" }, - "revokeSponsorshipConfirmation": { - "message": "Після вилучення цього облікового запису, спонсорування сімейного плану завершиться в кінці платіжного періоду. Ви не зможете запитати нову пропозицію спонсорування доки чинна не завершиться. Ви дійсно хочете продовжити?" - }, "removeSponsorshipSuccess": { "message": "Спонсорування вилучено" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Обов'язково, якщо ID об'єкта не є URL." }, + "offerNoLongerValid": { + "message": "Ця пропозиція застаріла. Зверніться до адміністраторів вашої організації для отримання додаткової інформації." + }, "openIdOptionalCustomizations": { "message": "Додаткові налаштування" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Генерувати е-пошту" }, - "generatorBoundariesHint": { - "message": "Значення має бути між $MIN$ та $MAX$", + "spinboxBoundariesHint": { + "message": "Значення має бути між $MIN$ та $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Тип імені користувача" }, @@ -6674,13 +6850,17 @@ "message": "Необхідна передплата преміум" }, "scim": { - "message": "Забезпечення SCIM", + "message": "Розгортання SCIM", "description": "The text, 'SCIM', is an acronym and should not be translated." }, "scimDescription": { "message": "Автоматично забезпечувати користувачів та групи бажаним провайдером ідентифікації через SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Автоматично забезпечувати користувачів та групи бажаним провайдером ідентифікації через SCIM. Знайти підтримувані інтеграції", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Увімкнути SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Ініційовано вхід" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Запам'ятайте цей пристрій, щоб спростити майбутні входи в систему" + }, "deviceApprovalRequired": { "message": "Необхідне підтвердження пристрою. Виберіть варіант підтвердження нижче:" }, + "deviceApprovalRequiredV2": { + "message": "Потрібне підтвердження пристрою" + }, + "selectAnApprovalOptionBelow": { + "message": "Виберіть варіант підтвердження нижче" + }, "rememberThisDevice": { "message": "Запам'ятати цей пристрій" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Схвалити запит" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Немає запитів з пристрою" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "Немає адреси електронної пошти" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Адресу е-пошти активного користувача не знайдено. Виконується вихід із системи." + }, "deviceTrusted": { "message": "Довірений пристрій" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Керувати налаштуваннями збірок для організації" }, - "limitCollectionCreationDeletionDesc": { - "message": "Дозволити створення та видалення збірок лише власникам та адміністраторам" - }, "limitCollectionCreationDesc": { "message": "Дозволити створення збірок лише власникам та адміністраторам" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "додайте спосіб оплати", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Інформація про організацію" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Використовуйте SDK менеджера секретів Bitwarden із зазначеними мовами програмування для створення власних програм." }, - "setUpGithubActions": { - "message": "Налаштувати дії для Github" + "ssoDescStart": { + "message": "Налаштувати", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "для Bitwarden, використовуючи посібник із впровадження для вашого провайдера ідентифікації.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "Розгортання користувача" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Налаштувати Kubernetes" + "scimIntegrationDescStart": { + "message": "Налаштувати ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Налаштувати GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(система керування ідентифікаційними даними між доменами), щоб автоматично розгортати користувачів та групи в Bitwarden, використовуючи посібник із впровадження для вашого провайдера ідентифікації.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Налаштувати Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "Переглянути репозиторій Rust" + "bwdcDesc": { + "message": "Налаштуйте Bitwarden Directory Connector, щоб автоматично розгортати користувачів та групи, використовуючи посібник із впровадження для вашого провайдера ідентифікації." }, - "cSharpSDKRepo": { - "message": "Перегляд репозиторію C#" + "eventManagement": { + "message": "Керування подіями" }, - "cPlusPlusSDKRepo": { - "message": "Перегляд репозиторію C++" + "eventManagementDesc": { + "message": "Інтегруйте журнали подій Bitwarden з вашою SIEM (керування системною інформацією та подіями), використовуючи посібник із впровадження для вашої платформи." }, - "jsWebAssemblySDKRepo": { - "message": "Перегляд репозиторію JS WebAssembly" + "deviceManagement": { + "message": "Керування пристроями" }, - "javaSDKRepo": { - "message": "Перегляд репозиторію Java" + "deviceManagementDesc": { + "message": "Налаштуйте керування пристроями для Bitwarden, використовуючи посібник із впровадження для вашої платформи." + }, + "integrationCardTooltip": { + "message": "Відкрити посібник із впровадження $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Налаштувати $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "Перегляд репозиторію Python" + "smSdkTooltip": { + "message": "Переглянути репозиторій $SDK$", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "Перегляд репозиторію php" + "integrationCardAriaLabel": { + "message": "відкрийте посібник із впровадження $INTEGRATION$ в новій вкладці.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "Перегляд репозиторію Ruby" + "smSdkAriaLabel": { + "message": "перегляньте репозиторій $SDK$ у новій вкладці.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "Перегляд репозиторію Go" + "smIntegrationCardAriaLabel": { + "message": "налаштуйте посібник із впровадження $INTEGRATION$ в новій вкладці.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Створіть нову організацію клієнта, щоб керувати нею як провайдер. Додаткові місця будуть відображені в наступному платіжному циклі." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Керований постачальник послуг" }, + "managedServiceProvider": { + "message": "Провайдер керованих послуг" + }, + "multiOrganizationEnterprise": { + "message": "Підприємство з розгалуженою організацією" + }, "orgSeats": { "message": "Місць в організації" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Податкову інформацію оновлено" }, + "billingInvalidTaxIdError": { + "message": "Недійсний ІПН. Якщо ви вважаєте це помилкою, зверніться до служби підтримки." + }, + "billingTaxIdTypeInferenceError": { + "message": "Не вдалося перевірити ваш ІПН. Якщо ви вважаєте це помилкою, зверніться до служби підтримки." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Недійсний ІПН. Якщо ви вважаєте це помилкою, зверніться до служби підтримки." + }, + "billingPreviewInvoiceError": { + "message": "Під час перегляду рахунку виникла помилка. Повторіть спробу пізніше." + }, "unverified": { "message": "Не перевірений" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "ГБ додаткового сховища" }, + "sshKeyAlgorithm": { + "message": "Алгоритм ключа" + }, + "sshKeyFingerprint": { + "message": "Цифровий відбиток" + }, + "sshKeyPrivateKey": { + "message": "Закритий ключ" + }, + "sshKeyPublicKey": { + "message": "Відкритий ключ" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 облікових записів Преміум" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Власне розміщення" }, - "verified-domain-single-org-warning": { - "message": "Підтвердження домену ввімкне політику єдиної організації." + "claim-domain-single-org-warning": { + "message": "Під час заявлення домену буде ввімкнено політику єдиної організації." }, "single-org-revoked-user-warning": { "message": "Невідповідних учасників буде відкликано. Адміністратори зможуть відновити учасників, коли ті покинуть всі інші організації." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "Якщо учасник видаляється, його обліковий запис Bitwarden разом з даними особистого сховища також остаточно видаляється. Дані збірок залишаються в організації. Щоб їх відновити, учасник повинен створити обліковий запис і приєднатися до організації знову.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "Ця дія призведе до остаточного видалення усіх записів, якими володіє $NAME$. Це не вплине на збірки.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "Ця дія призведе до остаточного видалення усіх записів, якими володіють зазначені учасники. Це не вплине на збірки.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Видалено $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "Користувача вилучено з організації. Всі пов'язані дані користувача видалено." + }, + "deletedUserId": { + "message": "Видалений користувач $ID$ - власник / адміністратор видалив обліковий запис користувача", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "Користувач $ID$ покинув організацію", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "Організацію $ORGANIZATION$ призупинено", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Зверніться до власника організації для консультації." + }, + "suspendedOwnerOrgMessage": { + "message": "Щоб повернути доступ до своєї організації, додайте спосіб оплати." + }, + "deleteMembers": { + "message": "Видалити учасників" + }, + "noSelectedMembersApplicable": { + "message": "Ця дія не застосовується для жодного з вибраних учасників." + }, + "deletedSuccessfully": { + "message": "Успішно видалено" + }, + "freeFamiliesSponsorship": { + "message": "Вилучити безплатне спонсорство сімейних тарифних планів Bitwarden" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Не дозволяти учасникам отримувати сімейний тарифний план через цю організацію." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Оплата з банківським рахунком доступна тільки для клієнтів у США. Вам необхідно буде підтвердити свій банківський рахунок. Ми здійснимо мікродепозит протягом наступних 1-2 робочих днів. Введіть код дескриптора з цього депозиту на сторінці оплати організації, щоб підтвердити банківський рахунок. Неможливість засвідчення банківського рахунку призведе до втрати платежу та припинення вашої передплати." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "Ми зробили мікродепозит на ваш банківський рахунок (це може зайняти 1-2 робочих дні). Введіть код із 6 цифр, що починається з \"SM\", який ви побачите в описі депозиту. Неможливість засвідчення банківського рахунку призведе до втрати платежу і припинення вашої передплати." + }, + "descriptorCode": { + "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": "Змінити адресу е-пошти" + }, + "removeMembers": { + "message": "Вилучити учасників" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Заявлені домени" + }, + "claimDomain": { + "message": "Заявити домен" + }, + "reclaimDomain": { + "message": "Повторно заявити домен" + }, + "claimDomainNameInputHint": { + "message": "Зразок: mydomain.com. Для піддоменів потрібно заявити окремі записи." + }, + "automaticClaimedDomains": { + "message": "Автоматично заявлені домени" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden намагатиметься заявити домен 3 рази впродовж 72 годин. Якщо не вдасться заявити домен, перевірте DNS-запис у вашого провайдера й заявіть його вручну. Якщо домен не буде заявлено протягом 7 днів, його буде вилучено з вашої організації." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ не заявлено. Перевірте свої DNS-записи.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Заявлено" + }, + "domainStatusUnderVerification": { + "message": "Проходить перевірку" + }, + "claimedDomainsDesc": { + "message": "Заявіть домен, щоб володіти всіма обліковими записами учасників, адреси е-пошти яких відповідають цьому домену. Учасники матимуть змогу пропустити ідентифікацію SSO під час входу в систему. Адміністратори також зможуть видаляти облікові записи учасників." + }, + "invalidDomainNameClaimMessage": { + "message": "Неправильний формат введення. Правильний формат: mydomain.com. Для заявлення піддоменів потрібно ввести окремі записи." + }, + "domainClaimedEvent": { + "message": "Домен $DOMAIN$ заявлено", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "Домен $DOMAIN$ не заявлено", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "Якщо ви видалите $EMAIL$, спонсорство для цього сімейного плану не можна буде використати. Ви дійсно хочете продовжити?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "Якщо ви видалите $EMAIL$, спонсорство для цього сімейного плану припиниться, а зі збереженого способу оплати буде стягнуто $40 + належний податок станом на $DATE$. Ви не зможете отримати нове спонсорство до $DATE$. Справді продовжити?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Домен заявлено" + }, + "organizationNameMaxLength": { + "message": "Назва організації не може перевищувати 50 символів." + }, + "resellerRenewalWarning": { + "message": "Ваша передплата невдовзі поновиться. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Рахунок за вашу передплату випущено $ISSUED_DATE$. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Рахунок за вашу передплату ще не сплачено. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index a19cd508793..ff9f789f6f4 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "Critical applications" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { "message": "Risk Insights" }, "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { "message": "Data last updated: $DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "Notified members" }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "Cannot restore organization access" + }, "allApplicationsWithCount": { "message": "All applications ($COUNT$)", "placeholders": { @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "Ghi chú" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "Thông tin đăng nhập" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "Đăng nhập bằng thiết bị phải được thiết lập trong cài đặt của ứng dụng Bitwarden. Dùng cách khác?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "Đăng nhập bằng mật khẩu chính" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "Xác minh danh tính của bạn" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "Phiên bản $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "Lịch sử mật khẩu" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "Chưa có mật khẩu." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Xóa", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "Hãy đăng nhập lại." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vui lòng đăng nhập lại. Nếu bạn đang dùng những ứng dụng Bitwarden khác, vui lòng đăng xuất and đăng nhập lại những ứng dụng đó." }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "Tất cả phiên đăng nhập đã bị gỡ" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "View all log in options" }, @@ -3714,6 +3780,15 @@ "device": { "message": "Thiết bị" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "Update browser" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "Join organization" }, @@ -4388,6 +4515,9 @@ "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "Miễn phí", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "Fingerprint" }, @@ -5537,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "Email sent" }, - "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" - }, "removeSponsorshipSuccess": { "message": "Sponsorship removed" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "Username type" }, @@ -6681,6 +6857,10 @@ "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "Enable SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "Login initiated" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "Device approval required. Select an approval option below:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "Remember this device" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "User email missing" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "Device trusted" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" }, - "limitCollectionCreationDeletionDesc": { - "message": "Limit collection creation and deletion to owners and admins" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "add a payment method", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "Organization information" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, - "setUpGithubActions": { - "message": "Set up Github Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." + }, + "userProvisioning": { + "message": "User provisioning" + }, + "scimIntegration": { + "message": "SCIM" }, - "setUpKubernetes": { - "message": "Set up Kubernetes" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "setUpAnsible": { - "message": "Set up Ansible" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "rustSDKRepo": { - "message": "View Rust repository" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "cSharpSDKRepo": { - "message": "View C# repository" + "eventManagement": { + "message": "Event management" }, - "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "deviceManagement": { + "message": "Device management" }, - "javaSDKRepo": { - "message": "View Java repository" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." + }, + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } }, - "pythonSDKRepo": { - "message": "View Python repository" + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } }, - "phpSDKRepo": { - "message": "View php repository" + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, - "rubySDKRepo": { - "message": "View Ruby repository" + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } }, - "goSDKRepo": { - "message": "View Go repository" + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9540,17 +9838,17 @@ "message": "Are you sure you want to permanently delete this attachment?" }, "manageSubscriptionFromThe": { - "message": "Quản lý đăng ký từ", + "message": "Manage subscription from the", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "Để lưu trữ Bitwarden trên máy chủ của riêng bạn, bạn sẽ cần tải tệp giấy phép của mình lên. Để hỗ trợ các gói Free Families và khả năng thanh toán nâng cao cho tổ chức tự lưu trữ của bạn, bạn sẽ cần thiết lập đồng bộ hóa tự động trong tổ chức tự lưu trữ của mình." + "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." }, "selfHostingTitleProper": { - "message": "Tự lưu trữ" + "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 360ca2920f9..ae0b3744ec6 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -5,14 +5,17 @@ "criticalApplications": { "message": "关键应用程序" }, + "accessIntelligence": { + "message": "Access Intelligence" + }, "riskInsights": { - "message": "Risk Insights" + "message": "风险洞察" }, "passwordRisk": { "message": "密码风险" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "跨应用程序审查有风险的密码(弱密码、暴露的密码或重复使用的密码)。选择最关键的应用程序,优先为用户采取安全措施,以解决密码风险问题。" }, "dataLastUpdated": { "message": "数据最后更新于:$DATE$", @@ -26,6 +29,15 @@ "notifiedMembers": { "message": "已通知的成员" }, + "revokeMembers": { + "message": "撤销成员" + }, + "restoreMembers": { + "message": "恢复成员" + }, + "cannotRestoreAccessError": { + "message": "无法恢复组织访问权限" + }, "allApplicationsWithCount": { "message": "所有应用程序($COUNT$)", "placeholders": { @@ -160,7 +172,7 @@ "message": "登录凭据" }, "personalDetails": { - "message": "个人信息" + "message": "个人详细信息" }, "identification": { "message": "身份" @@ -169,10 +181,10 @@ "message": "联系信息" }, "cardDetails": { - "message": "支付卡详情" + "message": "支付卡详细信息" }, "cardBrandDetails": { - "message": "$BRAND$ 详情", + "message": "$BRAND$ 详细信息", "placeholders": { "brand": { "content": "$1", @@ -273,7 +285,7 @@ "message": "许可证号码" }, "email": { - "message": "电子邮件" + "message": "电子邮箱" }, "phone": { "message": "电话" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "安全笔记" }, + "typeSshKey": { + "message": "SSH 密钥" + }, "typeLoginPlural": { "message": "登录" }, @@ -669,7 +684,7 @@ "message": "项目" }, "itemDetails": { - "message": "项目详情" + "message": "项目详细信息" }, "itemName": { "message": "项目名称" @@ -762,7 +777,7 @@ "message": "复制电话号码" }, "copyEmail": { - "message": "复制电子邮件地址" + "message": "复制电子邮箱" }, "copyCompany": { "message": "复制公司信息" @@ -943,13 +958,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他登录选项吗?" }, + "needAnotherOptionV1": { + "message": "需要其他选项吗?" + }, "loginWithMasterPassword": { "message": "使用主密码登录" }, @@ -988,7 +1006,7 @@ "message": "保持此窗口打开然后按照浏览器的提示操作。" }, "useADifferentLogInMethod": { - "message": "使用不同的登录方式" + "message": "使用其他登录方式" }, "logInWithPasskey": { "message": "使用通行密钥登录" @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "登录到 Bitwarden" }, + "authenticationTimeout": { + "message": "身份验证超时" + }, + "authenticationSessionTimedOut": { + "message": "身份验证会话超时。请重新启动登录过程。" + }, "verifyIdentity": { "message": "验证您的身份" }, + "whatIsADevice": { + "message": "什么是设备?" + }, + "aDeviceIs": { + "message": "设备是您登录过的 Bitwarden App 的独立安装。重新安装、清除 App 数据或清除 Cookie,可能会导致设备多次出现。" + }, "logInInitiated": { "message": "登录已发起" }, @@ -1111,7 +1141,7 @@ "message": "提交" }, "emailAddressDesc": { - "message": "使用您的电子邮件地址登录。" + "message": "使用您的电子邮箱地址登录。" }, "yourName": { "message": "您的姓名" @@ -1160,7 +1190,7 @@ "message": "设置" }, "accountEmail": { - "message": "账户邮件地址" + "message": "账户电子邮箱" }, "requestHint": { "message": "请求提示" @@ -1169,22 +1199,22 @@ "message": "请求密码提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "输入您的账户电子邮件地址,您的密码提示将发送给您" + "message": "输入您的账户电子邮箱地址,您的密码提示将发送给您" }, "passwordHint": { "message": "密码提示" }, "enterEmailToGetHint": { - "message": "输入您账户的电子邮件地址来接收主密码提示。" + "message": "请输入您的账户电子邮箱地址来接收主密码提示。" }, "getMasterPasswordHint": { "message": "获取主密码提示" }, "emailRequired": { - "message": "必须填写电子邮件地址。" + "message": "必须填写电子邮箱地址。" }, "invalidEmail": { - "message": "无效的电子邮件地址。" + "message": "无效的电子邮箱地址。" }, "masterPasswordRequired": { "message": "必须填写主密码。" @@ -1218,7 +1248,7 @@ "message": "账户创建成功。" }, "masterPassSent": { - "message": "我们已经为您发送了包含主密码提示的邮件。" + "message": "我们已经为您发送了包含主密码提示的电子邮件。" }, "unexpectedError": { "message": "发生意外错误。" @@ -1227,7 +1257,7 @@ "message": "请选择一个将来的过期日期。" }, "emailAddress": { - "message": "电子邮件地址" + "message": "电子邮箱地址" }, "yourVaultIsLockedV2": { "message": "您的密码库已锁定" @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "通知已发送到您的设备。" }, + "aNotificationWasSentToYourDevice": { + "message": "通知已发送到您的设备" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "确保您的账户已解锁,并且指纹短语与其他设备上的相匹配。" + }, "versionNumber": { "message": "版本: $VERSION_NUMBER$", "placeholders": { @@ -1309,7 +1345,7 @@ "message": "请输入您的验证器 App 中的 6 位数验证码。" }, "enterVerificationCodeEmail": { - "message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。", + "message": "请输入发送给 $EMAIL$ 的 6 位数验证码。", "placeholders": { "email": { "content": "$1", @@ -1377,7 +1413,7 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "为您的组织使用 Duo Security 的 Duo 移动应用、短信、电话或 U2F 安全钥匙来进行验证。", + "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全钥匙来进行验证。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "u2fDesc": { @@ -1396,10 +1432,10 @@ "message": "(迁移自 FIDO)" }, "emailTitle": { - "message": "电子邮件" + "message": "电子邮箱" }, "emailDescV2": { - "message": "输入发送到您的电子邮箱的代码。" + "message": "请输入发送到您的电子邮箱的代码。" }, "continue": { "message": "继续" @@ -1491,7 +1527,7 @@ "message": "确认机密导出" }, "exportWarningDesc": { - "message": "本次导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。用完后请立即将其删除。" + "message": "此导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。使用完后请立即将其删除。" }, "exportSecretsWarningDesc": { "message": "本次导出包含未加密格式的机密数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。用完后请立即将其删除。" @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "密码历史记录" }, + "generatorHistory": { + "message": "生成器历史记录" + }, + "clearGeneratorHistoryTitle": { + "message": "清除生成器历史记录" + }, + "cleargGeneratorHistoryDescription": { + "message": "若继续,所有条目将从生成器历史记录中永久删除。确定要继续吗?" + }, "noPasswordsInList": { "message": "没有可列出的密码。" }, + "clearHistory": { + "message": "清除历史记录" + }, + "nothingToShow": { + "message": "没有可显示的内容" + }, + "nothingGeneratedRecently": { + "message": "您最近没有生成任何内容" + }, "clear": { "message": "清除", "description": "To clear something out. Example: To clear browser history." @@ -1638,19 +1692,19 @@ "message": "账户已保存" }, "changeEmail": { - "message": "修改电子邮件地址" + "message": "更改电子邮箱" }, "changeEmailTwoFactorWarning": { - "message": "继续操作将更改您的账户电子邮件地址。这不会更改用于双重身份验证的电子邮件地址。您可以在两步登录设置中更改它。" + "message": "继续操作将更改您的账户电子邮箱地址。这不会更改用于双重身份验证的电子邮箱地址。您可以在两步登录设置中更改它。" }, "newEmail": { - "message": "新电子邮件地址" + "message": "新的电子邮箱" }, "code": { "message": "代码" }, "changeEmailDesc": { - "message": "我们已将验证码发送到 $EMAIL$。请检查您的电子邮件,在下方输入验证码,以确认更改您的电子邮件地址。", + "message": "我们已将验证码发送到 $EMAIL$。请检查您的电子邮箱,在下方输入验证码,以确认更改您的电子邮箱地址。", "placeholders": { "email": { "content": "$1", @@ -1659,16 +1713,22 @@ } }, "loggedOutWarning": { - "message": "接下来将会注销您当前的会话,要求您重新登录。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "emailChanged": { - "message": "电子邮件已保存" + "message": "电子邮箱已保存" }, "logBackIn": { "message": "请重新登录。" }, + "currentSession": { + "message": "当前会话" + }, + "requestPending": { + "message": "请求待处理" + }, "logBackInOthersToo": { - "message": "请重新登录。如果您还在使用其他 Bitwarden 应用,也请注销并重新登陆。" + "message": "请重新登录。如果您还在使用其他 Bitwarden 应用程序,也请注销并重新登陆。" }, "changeMasterPassword": { "message": "修改主密码" @@ -1744,16 +1804,16 @@ "message": "取消会话授权" }, "deauthorizeSessionsDesc": { - "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共电脑或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" + "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" }, "deauthorizeSessionsWarning": { - "message": "接下来将会注销您当前的会话,并要求您重新登录。如果有设置两步登录,也需要重新认证。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作还将使您退出当前会话,并要求您重新登录。如果有设置两步登录,也需要重新验证。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "sessionsDeauthorized": { "message": "已取消所有会话授权" }, - "accountIsManagedMessage": { - "message": "此账户由 $ORGANIZATIONNAME$ 管理", + "accountIsOwnedMessage": { + "message": "此账户由 $ORGANIZATIONNAME$ 拥有", "placeholders": { "organizationName": { "content": "$1", @@ -1949,13 +2009,13 @@ "message": "域名规则" }, "domainRulesDesc": { - "message": "如果您在多个不同网站之间使用同一个登陆信息,您可以把这些网站标记为「通用」。Bitwarden 会为您设置「全局」域名。" + "message": "如果您在多个不同网站域名中使用同一个登录信息,您可以把这些网站标记为「等效」。「全局」域名是由 Bitwarden 为您预先创建的域名。" }, "globalEqDomains": { - "message": "全局通用域名" + "message": "全局等效域名" }, "customEqDomains": { - "message": "自定义通用域名" + "message": "自定义等效域名" }, "exclude": { "message": "排除" @@ -1970,7 +2030,7 @@ "message": "添加自定义域名" }, "newCustomDomainDesc": { - "message": "输入用逗号分隔的域名列表。只能输入「基础」域名,不要输入子域名。例如,输入「google.com」而不是「www.google.com」。您也可以输入「androidapp://package.name」以将 Android 应用程序与其他网站域名关联。" + "message": "输入用逗号分隔的域名列表。只能输入「基础」域名,不要输入子域名。例如,输入「google.com」而不是「www.google.com」。您也可以输入「androidapp://package.name」以将 Android App 与其他网站域名关联。" }, "customDomainX": { "message": "自定义域名 $INDEX$", @@ -1991,7 +2051,7 @@ "message": "强制两步登录" }, "twoStepLoginDesc": { - "message": "在登录时要求使用额外的步骤来保护您的账户。" + "message": "在登录时要求执行额外的步骤来保护您的账户。" }, "twoStepLoginTeamsDesc": { "message": "为您的组织启用两步登录。" @@ -2007,7 +2067,7 @@ "message": "要实施 Duo 方式的两步登录,请使用下面的选项。" }, "twoStepLoginOrganizationSsoDesc": { - "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序实施了。" + "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序强制实施了。" }, "twoStepLoginRecoveryWarning": { "message": "启用两步登录可能会将您永久锁定在 Bitwarden 账户之外。如果您无法使用常规的两步登录提供程序(例如您丢失了设备),则可以使用恢复代码访问您的账户。如果您失去对您账户的访问,Bitwarden 支持也无法帮助您。我们建议您记下或打印恢复代码,并将其妥善保管。" @@ -2087,13 +2147,13 @@ } }, "continueToExternalUrlDesc": { - "message": "您将离开 Bitwarden 并将在新窗口中启动一个外部网站。" + "message": "您将离开 Bitwarden 并将在新窗口中打开一个外部网站。" }, "twoStepContinueToBitwardenUrlTitle": { "message": "前往 bitwarden.com 吗?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。在 bitwarden.com 网站上了解更多信息。" + "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。访问 bitwarden.com 网站了解更多信息。" }, "twoStepAuthenticatorScanCodeV2": { "message": "用您的验证器 App 扫描下面的二维码或输入密钥。" @@ -2120,7 +2180,7 @@ "message": "添加一个新的 YubiKey 到您的账户" }, "twoFactorYubikeyPlugIn": { - "message": "将 YubiKey 插入您电脑的 USB 端口。" + "message": "将 YubiKey 插入计算机的 USB 端口。" }, "twoFactorYubikeySelectKey": { "message": "在下面选择第一个空的 YubiKey 输入字段。" @@ -2132,13 +2192,13 @@ "message": "保存表单。" }, "twoFactorYubikeyWarning": { - "message": "由于平台的限制,YubiKey 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 YubiKey 时可以访问您的账户。支持的平台:" + "message": "由于平台限制,YubiKey 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 YubiKey 时可以访问您的账户。支持的平台:" }, "twoFactorYubikeySupportUsb": { - "message": "具有 USB 端口的设备上的网页版密码库、桌面应用程序、CLI 以及浏览器扩展都可以使用您的 YubiKey。" + "message": "具有可使用 YubiKey 的 USB 端口的设备上的网页版密码库、桌面应用程序、CLI 以及浏览器扩展。" }, "twoFactorYubikeySupportMobile": { - "message": "具有兼容 NFC 或数据端口的设备上的移动应用程序可以使用您的 YubiKey。" + "message": "具有 NFC 功能或可使用 YubiKey 的数据端口的设备上的移动 App。" }, "yubikeyX": { "message": "YubiKey $INDEX$", @@ -2183,7 +2243,7 @@ "message": "禁用全部钥匙" }, "twoFactorDuoDesc": { - "message": "输入 Duo 管理面板提供的 Bitwarden 应用信息。" + "message": "输入 Duo 管理面板提供的 Bitwarden 应用程序信息。" }, "twoFactorDuoClientId": { "message": "Client ID" @@ -2195,10 +2255,10 @@ "message": "API 主机名" }, "twoFactorEmailDesc": { - "message": "按照以下步骤设置使用电子邮件的两步登录:" + "message": "按照以下步骤设置使用电子邮箱的两步登录:" }, "twoFactorEmailEnterEmail": { - "message": "输入您希望接收验证码的电子邮件地址" + "message": "输入您希望用于接收验证码的电子邮箱" }, "twoFactorEmailEnterCode": { "message": "输入电子邮件中的 6 位数验证码" @@ -2234,7 +2294,7 @@ "message": "保存表单。" }, "twoFactorU2fWarning": { - "message": "由于平台的限制,FIDO U2F 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 FIDO U2F 时可以访问您的账户。支持的平台:" + "message": "由于平台限制,FIDO U2F 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 FIDO U2F 时可以访问您的账户。支持的平台:" }, "twoFactorU2fSupportWeb": { "message": "桌面/笔记本电脑上支持 U2F 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页版密码库和浏览器扩展。" @@ -2249,7 +2309,7 @@ "message": "读取安全钥匙时出现问题,请重试。" }, "twoFactorWebAuthnWarning": { - "message": "由于平台限制,无法在所有 Bitwarden 应用程序中使用 WebAuthn。您应该启用另一个两步登录提供程序,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" + "message": "由于平台限制,WebAuthn 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" }, "twoFactorWebAuthnSupportWeb": { "message": "桌面/笔记本电脑上支持 WebAuthn 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页密码库和浏览器扩展。" @@ -2440,7 +2500,7 @@ "message": "泄露的账户可能会暴露您的个人信息。通过启用 2FA 或创建更强大的密码来保护被泄露的账户。" }, "breachCheckUsernameEmail": { - "message": "检查您使用的任何用户名或电子邮件地址。" + "message": "检查您使用的任何用户名或电子邮箱地址。" }, "checkBreaches": { "message": "检查泄漏情况" @@ -2521,7 +2581,7 @@ "message": "付款处理完毕后,添加的信用额度将出现在您的账户上。某些付款方式会延迟,并且可能比其他方式需要更长的时间来处理。" }, "makeSureEnoughCredit": { - "message": "请确保您的账户有足够的信用额度来用于此购买。如果您的账户信用额度不足,您的默认付款方式将用于补足差额。您可以从计费页面向您的账户添加信用额度。" + "message": "请确保您的账户有足够的信用额度来用于此购买。如果您的账户信用额度不足,您的默认付款方式将用于补足差额。您可以从「计费」页面向您的账户添加信用额度。" }, "creditAppliedDesc": { "message": "您账户的信用额度可用于进行消费。任何可用的信用额度将用于自动支付此账户的账单。" @@ -2836,7 +2896,7 @@ "message": "联系支持" }, "updatedPaymentMethod": { - "message": "已更新付款方式。" + "message": "更新了付款方式。" }, "purchasePremium": { "message": "购买高级版" @@ -2860,7 +2920,7 @@ "message": "若要创建基于本地托管的组织,您需要上传有效的许可证文件。" }, "accountEmailMustBeVerified": { - "message": "您必须验证账户的电子邮件地址。" + "message": "必须验证您的账户电子邮箱地址。" }, "newOrganizationDesc": { "message": "组织允许您与他人共享您的密码库的部分内容,以及管理特定实体(例如家族、小型团队或大型公司)的相关用户。" @@ -2875,7 +2935,7 @@ "message": "此账户由商业用户拥有。" }, "billingEmail": { - "message": "计费电子邮箱地址" + "message": "计费电子邮箱" }, "businessName": { "message": "公司名称" @@ -3069,7 +3129,7 @@ } }, "trialConfirmationEmail": { - "message": "我们已经发送一封确认邮件到您的团队的计费电子邮箱 " + "message": "我们已经发送了一封确认邮件到您的团队的计费电子邮箱 " }, "monthly": { "message": "每月" @@ -3168,7 +3228,7 @@ "message": "外部 ID" }, "externalIdDesc": { - "message": "外部 ID 是一个 Bitwarden 目录连接器和 API 使用的未经加密的参考。" + "message": "外部 ID 是一个 Bitwarden Directory Connector 和 API 使用的未经加密的参考。" }, "nestCollectionUnder": { "message": "嵌套于集合下" @@ -3207,10 +3267,10 @@ } }, "inviteUserDesc": { - "message": "在下面输入 Bitwarden 账户的电子邮件地址,以邀请新用户加入您的组织。如果他们没有 Bitwarden 账户,将会提示他们创建一个。" + "message": "在下面输入 Bitwarden 账户电子邮箱地址,以邀请新用户加入您的组织。如果他们没有 Bitwarden 账户,将会提示他们创建一个。" }, "inviteMultipleEmailDesc": { - "message": "通过逗号分隔,最多输入 $COUNT$ 个电子邮件地址。", + "message": "通过逗号分隔,最多输入 $COUNT$ 个电子邮箱。", "placeholders": { "count": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "您还剩下 1 个邀请。" + }, "userUsingTwoStep": { "message": "此用户正在使用两步登录来保护他们的账户。" }, @@ -3231,7 +3294,7 @@ "message": "已确认" }, "clientOwnerEmail": { - "message": "客户所有者电子邮件" + "message": "客户所有者电子邮箱" }, "owner": { "message": "所有者" @@ -3246,7 +3309,7 @@ "message": "管理员" }, "adminDesc": { - "message": "管理组织访问权限,所有集合,成员,报告以及安全设置" + "message": "管理组织的访问权限,所有集合、成员、报告,以及安全设置" }, "user": { "message": "用户" @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "查看所有登录选项" + }, "viewAllLoginOptions": { "message": "查看所有登录选项" }, @@ -3714,8 +3780,17 @@ "device": { "message": "设备" }, + "loginStatus": { + "message": "登录状态" + }, + "firstLogin": { + "message": "首次登录" + }, + "trusted": { + "message": "信任" + }, "creatingAccountOn": { - "message": "正创建账户于" + "message": "创建账户至" }, "checkYourEmail": { "message": "检查您的电子邮箱" @@ -3733,7 +3808,7 @@ "message": "返回" }, "toEditYourEmailAddress": { - "message": "编辑您的电子邮件地址。" + "message": "编辑您的电子邮箱地址。" }, "view": { "message": "查看" @@ -3802,38 +3877,90 @@ "message": "结束日期" }, "verifyEmail": { - "message": "验证电子邮件" + "message": "验证电子邮箱" }, "verifyEmailDesc": { - "message": "验证您账户的电子邮件地址来解锁所有功能。" + "message": "验证您的账户电子邮箱地址以解锁所有功能。" }, "verifyEmailFirst": { - "message": "首先必须验证您账户的电子邮件地址。" + "message": "首先必须验证您的账户电子邮箱地址。" }, "checkInboxForVerification": { - "message": "检查您的电子邮件收件箱以获取验证链接。" + "message": "检查您的电子邮箱收件箱以获取验证链接。" }, "emailVerified": { - "message": "账户电子邮件已验证" + "message": "账户电子邮箱已验证" }, "emailVerifiedV2": { "message": "电子邮箱已验证" }, "emailVerifiedFailed": { - "message": "无法验证您的电子邮件。尝试发送新的验证电子邮件。" + "message": "无法验证您的电子邮箱。尝试发送新的验证电子邮件。" }, "emailVerificationRequired": { - "message": "需要验证电子邮件" + "message": "需要验证电子邮箱" }, "emailVerificationRequiredDesc": { - "message": "您必须验证您的电子邮件才能使用此功能。" + "message": "您必须验证电子邮箱才能使用此功能。" }, "updateBrowser": { "message": "更新浏览器" }, + "generatingRiskInsights": { + "message": "正在生成风险洞察..." + }, "updateBrowserDesc": { "message": "您使用的是不受支持的 Web 浏览器。网页密码库可能无法正常运行。" }, + "freeTrialEndPromptCount": { + "message": "您的免费试用将于 $COUNT$ 天后结束。", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$,您的免费试用将于 $COUNT$ 天后结束。", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$,您的免费试用将于明天结束。", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "您的免费试用将于明天结束。" + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$,您的免费试用将于今天结束。", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "您的免费试用将于今天结束。" + }, + "clickHereToAddPaymentMethod": { + "message": "点击这里添加付款方式。" + }, "joinOrganization": { "message": "加入组织" }, @@ -3856,7 +3983,7 @@ "message": "邀请已接受" }, "inviteAcceptedDesc": { - "message": "管理员确认您的成员资格后,您将能访问此组织。到时我们将向您发送电子邮件通知。" + "message": "管理员确认您的成员资格后,您就可以访问此组织了。届时我们会向您发送一封电子邮件。" }, "inviteInitAcceptedDesc": { "message": "您现在可以访问这个组织了。" @@ -3874,7 +4001,7 @@ } }, "rememberEmail": { - "message": "记住电子邮件地址" + "message": "记住电子邮箱" }, "recoverAccountTwoStepDesc": { "message": "如果您无法通过常规的两步登录方式访问您的账户,您可以使用两步登录恢复代码来停用账户上的所有两步登录提供程序。" @@ -3889,7 +4016,7 @@ "message": "进一步了解" }, "deleteRecoverDesc": { - "message": "请在下面输入您的电子邮件地址以恢复和删除您的账户。" + "message": "请在下面输入您的电子邮箱地址以恢复和删除您的账户。" }, "deleteRecoverEmailSent": { "message": "如果您的账户存在,我们已经向您发送了电子邮件,其中包含了进一步说明。" @@ -4183,7 +4310,7 @@ "message": "为了提高安全性,我们更改了加密方案。请在下方输入您的主密码以立即更新您的加密密钥。" }, "updateEncryptionKeyWarning": { - "message": "更新加密密钥后,您需要注销所有正在使用的 Bitwarden 应用(比如手机版应用或者浏览器扩展)后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但是,可能会有所延迟。" + "message": "更新加密密钥后,您需要注销所有正在使用的 Bitwarden 应用程序(比如移动 App 或者浏览器扩展)后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但是,可能会有所延迟。" }, "updateEncryptionKeyExportWarning": { "message": "您保存的任何已加密导出也将变为无效。" @@ -4201,7 +4328,7 @@ "message": "升级组织" }, "upgradeOrganizationDesc": { - "message": "本功能对免费组织不可用。切换到付费计划以解锁更多功能。" + "message": "此功能不适用于免费组织。请切换到付费计划以解锁更多功能。" }, "createOrganizationStep1": { "message": "创建组织:第一步" @@ -4228,7 +4355,7 @@ "message": "若继续,代表您同意" }, "and": { - "message": "以及" + "message": "和" }, "acceptPolicies": { "message": "选中此框表示您同意:" @@ -4240,7 +4367,7 @@ "message": "服务条款" }, "privacyPolicy": { - "message": "隐私条款" + "message": "隐私政策" }, "filters": { "message": "筛选" @@ -4388,6 +4515,9 @@ "message": "不再提示验证受邀用户的指纹短语(不推荐)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "请求获得批准后,您将收到通知" + }, "free": { "message": "免费", "description": "Free, as in 'Free beer'" @@ -4408,7 +4538,7 @@ "message": "您的 API 密钥可用于在 Bitwarden CLI 中进行身份验证。" }, "userApiKeyWarning": { - "message": "您的 API 密钥是另一套等效的身份验证机制。请严格保密。" + "message": "您的 API 密钥是一种替代身份验证机制。请严格保密。" }, "oauth2ClientCredentials": { "message": "OAuth 2.0 客户端凭据", @@ -4601,7 +4731,7 @@ "message": "包括 VAT/GST 信息(可选)" }, "taxIdNumber": { - "message": "VAT/GST 税号" + "message": "VAT/GST 税务 ID" }, "taxInfoUpdated": { "message": "税务信息已更新。" @@ -4616,7 +4746,13 @@ "message": "组织标识符" }, "ssoLogInWithOrgIdentifier": { - "message": "要使用您组织的单点登录门户登录。请首先输入您组织的标识符。" + "message": "使用您组织的单点登录门户登录。请输入您组织的 SSO 标识符以开始。" + }, + "singleSignOnEnterOrgIdentifier": { + "message": "输入您组织的 SSO 标识符以开始" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "要使用您的 SSO 提供程序登录,请输入您组织的 SSO 标识符以开始。当您从新设备登录时,可能需要输入此 SSO 标识符。" }, "enterpriseSingleSignOn": { "message": "企业单点登录" @@ -4707,7 +4843,7 @@ "message": "先决条件" }, "requireSsoPolicyReq": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "requireSsoPolicyReqError": { "message": "单一组织策略未启用。" @@ -4812,7 +4948,7 @@ "message": "确定移除此密码?" }, "hideEmail": { - "message": "对收件人隐藏我的电子邮件地址。" + "message": "对收件人隐藏我的电子邮箱地址。" }, "disableThisSend": { "message": "停用此 Send 则任何人无法访问它。", @@ -4893,7 +5029,7 @@ "message": "编辑紧急联系人" }, "inviteEmergencyContactDesc": { - "message": "通过在下面输入他们的 Bitwarden 账户电子邮件地址来邀请新的紧急联系人。如果他们还没有 Bitwarden 账户,将提示他们创建一个新账户。" + "message": "通过在下面输入他们的 Bitwarden 账户电子邮箱地址来邀请新的紧急联系人。如果他们还没有 Bitwarden 账户,将提示他们创建一个新账户。" }, "emergencyAccessRecoveryInitiated": { "message": "紧急访问已发起" @@ -4947,7 +5083,7 @@ } }, "emergencyInviteAcceptedDesc": { - "message": "身份确认后,您可以访问该用户的紧急选项。当发生这种情况时,我们会向您发送一封电子邮件。" + "message": "身份确认后,您就可以访问该用户的紧急选项了。届时我们会向您发送一封电子邮件。" }, "requestAccess": { "message": "请求访问权限" @@ -5046,14 +5182,14 @@ "message": "可以管理组织策略的组织成员豁免此策略。" }, "disableHideEmail": { - "message": "在创建或编辑 Send 时,始终向收件人显示成员的电子邮件地址。", + "message": "在创建或编辑 Send 时,始终向收件人显示成员的电子邮箱地址。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsPolicyInEffect": { "message": "以下组织策略目前正起作用:" }, "sendDisableHideEmailInEffect": { - "message": "用户在创建或编辑 Send 时不允许隐藏他们的电子邮件地址。", + "message": "创建或编辑 Send 时,不允许用户对收件人隐藏他们的电子邮箱地址。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "modifiedPolicyId": { @@ -5293,7 +5429,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or sign up to **try it today.**'" }, "sendAccessCreatorIdentifier": { - "message": "Bitwarden 成员 $USER_IDENTIFIER$ 与您共享了以下内容", + "message": "Bitwarden 成员 $USER_IDENTIFIER$ 与您分享了以下内容", "placeholders": { "user_identifier": { "content": "$1", @@ -5306,7 +5442,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { - "message": "创建此 Send 的 Bitwarden 用户已选择隐藏他们的电子邮件地址。在使用或下载此链接的内容之前,应确保您信任此链接的来源。", + "message": "创建此 Send 的 Bitwarden 用户已选择隐藏他们的电子邮箱地址。在使用或下载其内容之前,您应确保信任此链接的来源。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDateIsInvalid": { @@ -5433,7 +5569,7 @@ "message": "对于具有主密码的现有账户,需要成员自行注册后,管理员才可以恢复他们的账户。对于新的成员,自动注册后将打开账户恢复功能。" }, "accountRecoverySingleOrgRequirementDesc": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "resetPasswordPolicyAutoEnroll": { "message": "自动注册" @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "已拒绝,不适用于此操作" }, + "nonCompliantMembersTitle": { + "message": "不符合要求的成员" + }, + "nonCompliantMembersError": { + "message": "不符合单一组织策略或两步登录策略的成员在他们遵守策略要求之前无法被恢复" + }, "fingerprint": { "message": "指纹" }, @@ -5537,6 +5679,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "授予「管理账户恢复」权限时,必须同时授予「管理用户」权限" }, @@ -5575,7 +5731,7 @@ "message": "服务用户可以访问和管理所有客户组织。" }, "providerInviteUserDesc": { - "message": "通过在下面输入他们的 Bitwarden 账户电子邮件地址,以邀请新用户加入您的提供商。如果他们还没有 Bitwarden 账户,将提示他们创建一个。" + "message": "通过在下面输入他们的 Bitwarden 账户电子邮箱地址,以邀请新用户加入您的提供商。如果他们还没有 Bitwarden 账户,将提示他们创建一个新账户。" }, "joinProvider": { "message": "加入提供商" @@ -5675,10 +5831,10 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问此密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "masterPasswordInvalidWarning": { - "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -5847,7 +6003,7 @@ "message": "自定义用户 ID 声明类型" }, "additionalEmailClaimTypes": { - "message": "电子邮件声明类型" + "message": "电子邮箱声明类型" }, "additionalNameClaimTypes": { "message": "自定义名称声明类型" @@ -5880,16 +6036,16 @@ "message": "最小入站签名算法" }, "spWantAssertionsSigned": { - "message": "希望断言被签名" + "message": "要求使用签名的断言" }, "spValidateCertificates": { "message": "验证证书" }, "spUniqueEntityId": { - "message": "设置一个唯一的 SP 实体 ID" + "message": "设置专属的 SP 实体 ID" }, "spUniqueEntityIdDesc": { - "message": "生成您的组织独有的标识符" + "message": "为您的组织生成专属的标识符" }, "idpEntityId": { "message": "实体 ID" @@ -5925,7 +6081,7 @@ "message": "免费 Bitwarden 家庭" }, "sponsoredFamiliesEligible": { - "message": "您和您的家人有资格享受免费的 Bitwarden 家庭版计划。即使您不在公司上班,您也可以使用个人电子邮件兑换此计划,以保护您的数据安全。" + "message": "您和您的家人有资格享受免费的 Bitwarden 家庭版计划。即使您不在公司上班,您也可以使用个人电子邮箱兑换此计划,以保护您的数据安全。" }, "sponsoredFamiliesEligibleCard": { "message": "立即兑换免费的 Bitwarden 家庭版计划,即使您不在公司上班也能确保您的数据安全。" @@ -5943,7 +6099,7 @@ "message": "链接已失效。请让赞助方重新发送邀请。" }, "reclaimedFreePlan": { - "message": "已回收免费计划" + "message": "重新认领免费计划" }, "redeem": { "message": "兑换" @@ -5955,7 +6111,7 @@ "message": "您想兑换哪一个免费家庭邀请?" }, "sponsoredFamiliesEmail": { - "message": "输入您的个人电子邮件以兑换 Bitwarden 家庭" + "message": "输入您的个人电子邮箱以兑换 Bitwarden 家庭" }, "sponsoredFamiliesLeaveCopy": { "message": "如果您移除邀请或邀请被赞助组织移除,您的家庭赞助将在下一个续费日到期。" @@ -6029,9 +6185,6 @@ "emailSent": { "message": "电子邮件已发送" }, - "revokeSponsorshipConfirmation": { - "message": "移除该账户后,家庭计划赞助将在计费周期结束时到期。在现有的赞助到期之前您将无法兑换新的赞助邀请。确定要继续吗?" - }, "removeSponsorshipSuccess": { "message": "赞助已移除" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "如果实体 ID 不是一个 URL,则必填。" }, + "offerNoLongerValid": { + "message": "此优惠不再可用。请联系组织管理员获取更多信息。" + }, "openIdOptionalCustomizations": { "message": "个性化(可选)" }, @@ -6411,10 +6567,10 @@ "message": "生成用户名" }, "generateEmail": { - "message": "生成邮件地址" + "message": "生成电子邮箱" }, - "generatorBoundariesHint": { - "message": "值必须在 $MIN$ 和 $MAX$ 之间", + "spinboxBoundariesHint": { + "message": "值必须在 $MIN$ 和 $MAX$ 之间。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,18 +6583,38 @@ } } }, + "passwordLengthRecommendationHint": { + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "用户名类型" }, "plusAddressedEmail": { - "message": "附加地址电子邮件", + "message": "附加地址电子邮箱", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "使用您的电子邮件提供商的子地址功能。" + "message": "使用您的电子邮箱提供商的子地址功能。" }, "catchallEmail": { - "message": "Catch-all 电子邮件" + "message": "Catch-all 电子邮箱" }, "catchallEmailDesc": { "message": "使用您的域名配置的 Catch-all 收件箱。" @@ -6477,7 +6653,7 @@ "message": "未知项目,你可能需要申请权限才能访问这个项目。" }, "cannotSponsorSelf": { - "message": "您不能为已启用的账户兑换。请输入其他电子邮件地址。" + "message": "您不能为活动账户兑换。请输入其他电子邮箱。" }, "revokeWhenExpired": { "message": "$DATE$ 到期", @@ -6527,13 +6703,13 @@ "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "转发的电子邮件别名" + "message": "转发的电子邮箱别名" }, "forwardedEmailDesc": { - "message": "使用外部转发服务生成一个电子邮件别名。" + "message": "使用外部转发服务生成一个电子邮箱别名。" }, "forwarderDomainName": { - "message": "邮件域名", + "message": "电子邮箱域名", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -6593,7 +6769,7 @@ } }, "forwarderNoAccountId": { - "message": "无法获取 $SERVICENAME$ 电子邮件账户 ID。", + "message": "无法获取 $SERVICENAME$ 电子邮箱账户 ID。", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -6656,13 +6832,13 @@ "message": "启用设备验证" }, "deviceVerificationDesc": { - "message": "登录未识别的设备时,验证码会发送到您的电子邮箱" + "message": "登录未识别的设备时,验证码会发送到您的电子邮箱地址" }, "updatedDeviceVerification": { "message": "已更新设备验证" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "确定要开启设备验证吗?验证码邮件将发送到 $EMAIL$", + "message": "确定要开启设备验证吗?验证码电子邮件将发送到:$EMAIL$", "placeholders": { "email": { "content": "$1", @@ -6681,6 +6857,10 @@ "message": "通过 SCIM 配置,使用您首选的身份提供程序自动配置用户和群组", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "通过 SCIM 配置,使用您首选的身份提供程序自动配置用户和群组。查找支持的集成", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "启用 SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -6731,7 +6911,7 @@ "message": "必须输入内容。" }, "inputEmail": { - "message": "输入的不是电子邮件地址。" + "message": "输入的不是电子邮箱地址。" }, "inputMinLength": { "message": "至少输入 $COUNT$ 个字符。", @@ -6779,10 +6959,10 @@ } }, "multipleInputEmails": { - "message": "一个或多个电子邮件地址无效" + "message": "一个或多个电子邮箱无效" }, "tooManyEmails": { - "message": "您一次只能提交最多 $COUNT$ 个电子邮件地址", + "message": "您一次只能提交最多 $COUNT$ 个电子邮箱", "placeholders": { "count": { "content": "$1", @@ -6812,7 +6992,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 寻求帮助。" + "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 获取协助。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 Duo 然后按照步骤完成登录。" @@ -7919,7 +8099,7 @@ } }, "masterPasswordMinimumlength": { - "message": "主密码长度最少为 $LENGTH$ 个字符。", + "message": "主密码长度必须至少为 $LENGTH$ 个字符。", "placeholders": { "length": { "content": "$1", @@ -7935,7 +8115,7 @@ "message": "忽略" }, "notAvailableForFreeOrganization": { - "message": "免费组织不能使用此功能。请联系您的组织所有者寻求升级。" + "message": "此功能不适用于免费组织。请联系您的组织所有者寻求升级。" }, "smProjectSecretsNoItemsNoAccess": { "message": "请联系您的组织的管理员来管理此工程的机密。", @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "登录已发起" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "记住此设备以便将来无缝登录" + }, "deviceApprovalRequired": { "message": "需要设备批准。请在下面选择一个批准选项:" }, + "deviceApprovalRequiredV2": { + "message": "需要设备批准" + }, + "selectAnApprovalOptionBelow": { + "message": "在下方选择一个批准选项" + }, "rememberThisDevice": { "message": "记住此设备" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "批准请求" }, + "deviceApproved": { + "message": "设备已批准" + }, + "deviceRemoved": { + "message": "设备已移除" + }, + "removeDevice": { + "message": "移除设备" + }, + "removeDeviceConfirmation": { + "message": "确定要移除此设备吗?" + }, "noDeviceRequests": { "message": "无设备请求" }, @@ -8188,7 +8389,10 @@ "message": "登录已批准" }, "userEmailMissing": { - "message": "缺少用户电子邮件" + "message": "缺少用户电子邮箱" + }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "未找到活动的用户电子邮箱。您将被注销。" }, "deviceTrusted": { "message": "设备已信任" @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "管理组织的集合行为" }, - "limitCollectionCreationDeletionDesc": { - "message": "限制为仅所有者和管理员可以创建和删除集合" - }, "limitCollectionCreationDesc": { "message": "限制为仅所有者和管理员可以创建集合" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "请添加一个付款方式。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "组织信息" @@ -8453,7 +8654,7 @@ "message": "感谢您注册 Bitwarden 机密管理器!" }, "smFreeTrialConfirmationEmail": { - "message": "我们已经发送了一封确认邮件到 " + "message": "我们已经发送了一封确认电子邮件到 " }, "sorryToSeeYouGo": { "message": "很遗憾看到您离开!请分享您取消的原因,以帮助改进 Bitwarden。", @@ -8824,7 +9025,7 @@ } }, "deleteProviderWarningDescription": { - "message": "删除 $ID$ 之前,您必须取消链接所有的客户。", + "message": "您必须取消链接所有的客户,然后才能删除 $ID$。", "placeholders": { "id": { "content": "$1", @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "使用以下编程语言中的 Bitwarden 机密管理器 SDK 来构建您自己的应用程序。" }, - "setUpGithubActions": { - "message": "设置 Github Actions" + "ssoDescStart": { + "message": "配置", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "设置 Kubernetes" + "ssoDescEnd": { + "message": "为 Bitwarden(使用您的身份提供程序的实施指南)。", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "设置 GitLab CI/CD" + "userProvisioning": { + "message": "用户配置" }, - "setUpAnsible": { - "message": "设置 Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "查看 Rust 存储库" + "scimIntegrationDescStart": { + "message": "配置 ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "查看 C# 存储库" + "scimIntegrationDescEnd": { + "message": "(跨域身份管理系统)(使用您的身份提供程序的实施指南)以自动向 Bitwarden 配置用户和群组。", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "查看 C++ 存储库" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "查看 JS WebAssembly 存储库" + "bwdcDesc": { + "message": "使用您的身份提供程序的实施指南配置 Bitwarden Directory Connector 以自动配置用户和群组。" }, - "javaSDKRepo": { - "message": "查看 Java 存储库" + "eventManagement": { + "message": "事件管理" }, - "pythonSDKRepo": { - "message": "查看 Python 存储库" + "eventManagementDesc": { + "message": "使用适合您平台的实施指南将 Bitwarden 事件日志与您的 SIEM(系统信息和事件管理)系统集成。" }, - "phpSDKRepo": { - "message": "查看 php 存储库" + "deviceManagement": { + "message": "设备管理" }, - "rubySDKRepo": { - "message": "查看 Ruby 存储库" + "deviceManagementDesc": { + "message": "使用适合您平台的实施指南为 Bitwarden 配置设备管理。" }, - "goSDKRepo": { - "message": "查看 Go 存储库" + "integrationCardTooltip": { + "message": "打开 $INTEGRATION$ 实施指南。", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "设置 $INTEGRATION$。", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "查看 $SDK$ 存储库", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "在新标签页中打开 $INTEGRATION$ 实施指南。", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "在新标签页中查看 $SDK$ 存储库。", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "在新标签页中设置 $INTEGRATION$ 实施指南。", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "创建一个新的客户组织作为提供商来管理。附加席位将反映在下一个计费周期中。" @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "托管服务提供商" }, + "managedServiceProvider": { + "message": "托管服务提供商" + }, + "multiOrganizationEnterprise": { + "message": "多组织企业" + }, "orgSeats": { "message": "组织席位" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "更新了税务信息" }, + "billingInvalidTaxIdError": { + "message": "无效的税务 ID,如有疑问,请联系支持。" + }, + "billingTaxIdTypeInferenceError": { + "message": "我们无法验证您的税务 ID,如有疑问,请联系支持。" + }, + "billingPreviewInvalidTaxIdError": { + "message": "无效的税务 ID,如有疑问,请联系支持。" + }, + "billingPreviewInvoiceError": { + "message": "预览账单时出错。请稍后再试。" + }, "unverified": { "message": "未验证" }, @@ -9072,7 +9346,7 @@ "message": "没有可列出的客户" }, "providerBillingEmailHint": { - "message": "此电子邮件地址将用于接收与此供应商相关的所有账单", + "message": "此电子邮箱地址将用于接收与此提供商相关的所有账单", "description": "A hint that shows up on the Provider setup page to inform the admin the billing email will receive the provider's invoices." }, "upgradeOrganizationEnterprise": { @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB 附加存储" }, + "sshKeyAlgorithm": { + "message": "密钥算法" + }, + "sshKeyFingerprint": { + "message": "指纹" + }, + "sshKeyPrivateKey": { + "message": "私钥" + }, + "sshKeyPublicKey": { + "message": "公钥" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 个高级账户" }, @@ -9490,10 +9788,10 @@ "message": "对于如密码之类的敏感数据,请使用隐藏型字段" }, "checkBoxHelpText": { - "message": "如果您想自动勾选表单复选框(例如记住电子邮件地址),请使用复选框" + "message": "如果您想自动勾选表单复选框(例如记住电子邮箱),请使用复选框型字段" }, "linkedHelpText": { - "message": "当您处理特定网站的自动填充问题时,请使用链接型字段。" + "message": "当您处理特定网站的自动填充问题时,请使用链接型字段" }, "linkedLabelHelpText": { "message": "输入字段的 html ID、名称、aria-label 或占位符。" @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "自托管" }, - "verified-domain-single-org-warning": { - "message": "验证域名将启用单一组织策略。" + "claim-domain-single-org-warning": { + "message": "声明域名将启用单一组织策略。" }, "single-org-revoked-user-warning": { "message": "不符合要求的成员将被撤销。管理员可以在他们离开所有其他组织后恢复其成员资格。" @@ -9565,12 +9863,22 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "当成员被删除时,他们的 Bitwarden 账户和个人密码库数据将被永久删除。集合数据将保留在组织中。要恢复它们,他们必须创建一个账户并重新加入。", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "这将永久删除 $NAME$ 拥有的所有项目。集合中的项目不受影响。", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "这将永久删除以下成员拥有的所有项目。集合中的项目不受影响。", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "已删除 $NAME$", + "message": "删除了 $NAME$", "placeholders": { "name": { "content": "$1", @@ -9579,6 +9887,254 @@ } }, "organizationUserDeletedDesc": { - "message": "该用户已从组织中删除,所有关联的用户数据已被删除。" + "message": "该用户已从组织中删除,所有关联的用户数据也已删除。" + }, + "deletedUserId": { + "message": "删除了用户 $ID$ - 某个所有者/管理员删除了此用户账户", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "用户 $ID$ 离开了组织", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "$ORGANIZATION$ 已停用", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "联系您的组织所有者获取协助。" + }, + "suspendedOwnerOrgMessage": { + "message": "要重新访问您的组织,请添加一个付款方式。" + }, + "deleteMembers": { + "message": "删除成员" + }, + "noSelectedMembersApplicable": { + "message": "此操作不适用于任何选定的成员。" + }, + "deletedSuccessfully": { + "message": "删除成功" + }, + "freeFamiliesSponsorship": { + "message": "移除免费的 Bitwarden 家庭赞助" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "不允许成员通过此组织兑换家庭计划。" + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "使用银行账户付款仅对美国用户开放。您将被要求验证您的银行账户。我们将在 1-2 个工作日内进行一笔小额转账,请在组织的计费页面输入该转账的语句描述符代码以验证银行账户。验证银行账户失败将会错过支付,您的订阅将失效。" + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "我们已向您的银行账户存入了一笔小额转账(可能需要 1-2 个工作日到账)。请输入转账说明中以 \"SM\" 开头的六位数代码。验证银行账户失败将会错过支付,您的订阅将失效。" + }, + "descriptorCode": { + "message": "描述符代码" + }, + "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": "更改账户电子邮箱" + }, + "removeMembers": { + "message": "移除成员" + }, + "devices": { + "message": "设备" + }, + "deviceListDescription": { + "message": "您的账户在以下设备上登录过。如果您无法识别某个设备,请立即将其移除。" + }, + "deviceListDescriptionTemp": { + "message": "您的账户在以下设备上登录过。" + }, + "claimedDomains": { + "message": "已声明的域名" + }, + "claimDomain": { + "message": "声明域名" + }, + "reclaimDomain": { + "message": "重新声明域名" + }, + "claimDomainNameInputHint": { + "message": "示例:mydomain.com。子域名需要单独的条目才能声明。" + }, + "automaticClaimedDomains": { + "message": "自动声明域名" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden 将在前 72 小时内尝试声明域名 3 次。如果无法声明此域名,请检查主机中的 DNS 记录并手动声明。如果未声明,该域名将在 7 天内从您的组织中移除。" + }, + "domainNotClaimed": { + "message": "$DOMAIN$ 未声明。请检查 DNS 记录。", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "已声明" + }, + "domainStatusUnderVerification": { + "message": "正在验证" + }, + "claimedDomainsDesc": { + "message": "声明一个域名,以拥有电子邮箱地址与该域名匹配的所有成员账户。成员登录时将可以跳过 SSO 标识符。管理员也可以删除成员账户。" + }, + "invalidDomainNameClaimMessage": { + "message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目才能声明。" + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ 已声明", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ 未声明", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "如果您移除 $EMAIL$,将无法兑换此家庭计划赞助,确定要继续吗?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "如果您移除 $EMAIL$,此家庭计划赞助将终止,并且将于 $DATE$ 向已保存的付款方式收取 $40 + 相关税费。在 $DATE$ 之前您将无法兑换新的赞助。确定要继续吗?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "域名已声明" + }, + "organizationNameMaxLength": { + "message": "组织名称不能超过 50 个字符。" + }, + "resellerRenewalWarning": { + "message": "您的订阅即将续订。为确保服务不中断,请在 $RENEWAL_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "您的订阅账单已于 $ISSUED_DATE$ 开具。为确保服务不中断,请在 $DUE_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "您的订阅账单尚未支付。为确保服务不中断,请在 $GRACE_PERIOD_END$ 之前联系 $RESELLER$ 确认您的续订。", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "组织订阅已重启" + }, + "restartSubscription": { + "message": "重启您的订阅" + }, + "suspendedManagedOrgMessage": { + "message": "联系 $PROVIDER$ 获取协助。", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 7673ad3be30..002d0d55e0f 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -3,7 +3,10 @@ "message": "All applications" }, "criticalApplications": { - "message": "Critical applications" + "message": "重要應用程式" + }, + "accessIntelligence": { + "message": "存取資訊" }, "riskInsights": { "message": "Risk Insights" @@ -11,11 +14,11 @@ "passwordRisk": { "message": "Password Risk" }, - "discoverAtRiskPasswords": { - "message": "Discover at-risk passwords and notify users to change those passwords." + "reviewAtRiskPasswords": { + "message": "檢視全部應用中具有風險的密碼 (弱、被暴露或重複使用)。選擇最重要的應用程式並優先採取安全措施,幫助使用者解決具有風險的密碼。" }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "上次資料更新日期:$DATE$", "placeholders": { "date": { "content": "$1", @@ -24,10 +27,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "已被通知的成員" + }, + "revokeMembers": { + "message": "Revoke members" + }, + "restoreMembers": { + "message": "Restore members" + }, + "cannotRestoreAccessError": { + "message": "無法還原組織存取" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "全部應用程式($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -36,10 +48,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "新增登入項目" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "重要應用程式($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,7 +60,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "已被通知的成員($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -57,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "在$ORG NAME$中找無應用程式", "placeholders": { "org name": { "content": "$1", @@ -66,46 +78,46 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "在使用者儲存登入後,應用程式將會顯示在這裡,並同時顯示具有風險的密碼。標註重要應用程式並通知使用者以更新密碼。" }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "你並未將任何應用程式標註為重要" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "選擇你最重要的應用程式以查找具有風險的密碼,並通知使用者以更改這些密碼。" }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "標註重要應用程式" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "標註應用程式為重要" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "被標註重要的應用程式" }, "application": { "message": "Application" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "具有風險的密碼" }, "requestPasswordChange": { "message": "Request password change" }, "totalPasswords": { - "message": "Total passwords" + "message": "全部密碼" }, "searchApps": { "message": "Search applications" }, "atRiskMembers": { - "message": "At-risk members" + "message": "具有風險的成員" }, "totalMembers": { "message": "Total members" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "具有風險的應用程式" }, "totalApplications": { "message": "Total applications" @@ -559,6 +571,9 @@ "typeSecureNote": { "message": "安全筆記" }, + "typeSshKey": { + "message": "SSH key" + }, "typeLoginPlural": { "message": "登入資料" }, @@ -978,6 +993,9 @@ "loginWithDeviceEnabledNote": { "message": "必須先在 Bitwarden 應用程式設定中開啟後,才可以使用裝置登入。要改用其他選項嗎?" }, + "needAnotherOptionV1": { + "message": "Need another option?" + }, "loginWithMasterPassword": { "message": "使用主密碼登入" }, @@ -1101,9 +1119,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "authenticationTimeout": { + "message": "Authentication timeout" + }, + "authenticationSessionTimedOut": { + "message": "The authentication session timed out. Please restart the login process." + }, "verifyIdentity": { "message": "核實你的身份" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "登入已發起" }, @@ -1296,6 +1326,12 @@ "notificationSentDevice": { "message": "通知已傳送至您的裝置。" }, + "aNotificationWasSentToYourDevice": { + "message": "A notification was sent to your device" + }, + "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { + "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + }, "versionNumber": { "message": "版本 $VERSION_NUMBER$", "placeholders": { @@ -1627,9 +1663,27 @@ "passwordHistory": { "message": "密碼歷史記錄" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "沒有可列出的密碼。" }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "清除", "description": "To clear something out. Example: To clear browser history." @@ -1667,6 +1721,12 @@ "logBackIn": { "message": "請重新登入。" }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "請重新登入。若您還在使用其他 Bitwarden 應用程式,也請登出後再重新登入。" }, @@ -1752,8 +1812,8 @@ "sessionsDeauthorized": { "message": "已取消所有工作階段授權" }, - "accountIsManagedMessage": { - "message": "This account is managed by $ORGANIZATIONNAME$", + "accountIsOwnedMessage": { + "message": "This account is owned by $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3218,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "此使用者正在使用兩步驟登入保護帳戶。" }, @@ -3381,6 +3444,9 @@ } } }, + "viewAllLogInOptions": { + "message": "View all log in options" + }, "viewAllLoginOptions": { "message": "檢視所有登入選項" }, @@ -3714,6 +3780,15 @@ "device": { "message": "裝置" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "建立帳號於" }, @@ -3831,9 +3906,61 @@ "updateBrowser": { "message": "更新瀏覽器" }, + "generatingRiskInsights": { + "message": "Generating your risk insights..." + }, "updateBrowserDesc": { "message": "未支援您使用的瀏覽器。網頁版密碼庫可能無法正常運作。" }, + "freeTrialEndPromptCount": { + "message": "Your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$1", + "example": "remaining days" + } + } + }, + "freeTrialEndPromptMultipleDays": { + "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "placeholders": { + "count": { + "content": "$2", + "example": "remaining days" + }, + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrow": { + "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndPromptTomorrowNoOrgName": { + "message": "Your free trial ends tomorrow." + }, + "freeTrialEndPromptToday": { + "message": "$ORGANIZATION$, your free trial ends today.", + "placeholders": { + "organization": { + "content": "$1", + "example": "organization name" + } + } + }, + "freeTrialEndingTodayWithoutOrgName": { + "message": "Your free trial ends today." + }, + "clickHereToAddPaymentMethod": { + "message": "Click here to add a payment method." + }, "joinOrganization": { "message": "加入組織" }, @@ -4388,6 +4515,9 @@ "message": "不再提示驗證已邀請使用者的指紋短語(不推薦)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, + "youWillBeNotifiedOnceTheRequestIsApproved": { + "message": "You will be notified once the request is approved" + }, "free": { "message": "免費", "description": "Free, as in 'Free beer'" @@ -4618,6 +4748,12 @@ "ssoLogInWithOrgIdentifier": { "message": "若要使用組織的單一登入入口登入。請先輸入您的組織識別碼。" }, + "singleSignOnEnterOrgIdentifier": { + "message": "Enter your organization's SSO identifier to begin" + }, + "singleSignOnEnterOrgIdentifierText": { + "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + }, "enterpriseSingleSignOn": { "message": "企業單一登入" }, @@ -5522,6 +5658,12 @@ "bulkFilteredMessage": { "message": "已排除,不適用於此動作" }, + "nonCompliantMembersTitle": { + "message": "Non-compliant members" + }, + "nonCompliantMembersError": { + "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + }, "fingerprint": { "message": "指紋" }, @@ -5537,6 +5679,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "管理使用者也必須被授予管理帳戶復原的權限" }, @@ -6029,9 +6185,6 @@ "emailSent": { "message": "郵件已寄出" }, - "revokeSponsorshipConfirmation": { - "message": "移除此帳戶後,家庭方案贊助將在計費周期結束前逾期。在現有的贊助逾期前您將不能兌換新的贊助邀請。您確定要繼續嗎?" - }, "removeSponsorshipSuccess": { "message": "已移除贊助" }, @@ -6321,6 +6474,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "若 Entity ID 非 URL,則必須填入。" }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "自訂(選填)" }, @@ -6413,8 +6569,8 @@ "generateEmail": { "message": "Generate email" }, - "generatorBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$", + "spinboxBoundariesHint": { + "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6427,6 +6583,26 @@ } } }, + "passwordLengthRecommendationHint": { + "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "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": { + "content": "$1", + "example": "14" + } + } + }, + "passphraseNumWordsRecommendationHint": { + "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "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": { + "content": "$1", + "example": "6" + } + } + }, "usernameType": { "message": "使用者名稱類型" }, @@ -6681,6 +6857,10 @@ "message": "透過 SCIM 佈建,使用您首選的身分提供程式自動佈建使用者和群組", "description": "the text, 'SCIM', is an acronym and should not be translated." }, + "scimIntegrationDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "description": "the text, 'SCIM', is an acronym and should not be translated." + }, "scimEnabledCheckboxDesc": { "message": "啟用 SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." @@ -7958,9 +8138,18 @@ "loginInitiated": { "message": "登入已啟動" }, + "rememberThisDeviceToMakeFutureLoginsSeamless": { + "message": "Remember this device to make future logins seamless" + }, "deviceApprovalRequired": { "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, + "deviceApprovalRequiredV2": { + "message": "Device approval required" + }, + "selectAnApprovalOptionBelow": { + "message": "Select an approval option below" + }, "rememberThisDevice": { "message": "記住這個裝置" }, @@ -8085,6 +8274,18 @@ "approveRequest": { "message": "核准要求" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "沒有裝置要求" }, @@ -8190,6 +8391,9 @@ "userEmailMissing": { "message": "缺少使用者電子郵件地址" }, + "activeUserEmailNotFoundLoggingYouOut": { + "message": "Active user email not found. Logging you out." + }, "deviceTrusted": { "message": "裝置已信任" }, @@ -8285,9 +8489,6 @@ "collectionManagementDesc": { "message": "管理組織分類的行為" }, - "limitCollectionCreationDeletionDesc": { - "message": "對擁有者和管理員限制集合的建立和刪除" - }, "limitCollectionCreationDesc": { "message": "Limit collection creation to owners and admins" }, @@ -8441,7 +8642,7 @@ }, "addAPaymentMethod": { "message": "新增付款方式", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { "message": "組織資訊" @@ -8866,44 +9067,99 @@ "sdksDesc": { "message": "使用 Bitwarden 機密管理軟體開發工具包於下列程式語言來建立你自己的應用程式。" }, - "setUpGithubActions": { - "message": "設定 GitHub Actions" + "ssoDescStart": { + "message": "Configure", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpKubernetes": { - "message": "設定 Kubernetes" + "ssoDescEnd": { + "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "setUpGitlabCICD": { - "message": "設定 GitLab CI/CD" + "userProvisioning": { + "message": "User provisioning" }, - "setUpAnsible": { - "message": "設定 Ansible" + "scimIntegration": { + "message": "SCIM" }, - "rustSDKRepo": { - "message": "檢視 Rust 儲存庫" + "scimIntegrationDescStart": { + "message": "Configure ", + "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cSharpSDKRepo": { - "message": "檢視 C# 儲存庫" + "scimIntegrationDescEnd": { + "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "cPlusPlusSDKRepo": { - "message": "檢視 C++ 儲存庫" + "bwdc": { + "message": "Bitwarden Directory Connector" }, - "jsWebAssemblySDKRepo": { - "message": "檢視 JS WebAssembly 儲存庫" + "bwdcDesc": { + "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "javaSDKRepo": { - "message": "檢視 Java 儲存庫" + "eventManagement": { + "message": "Event management" }, - "pythonSDKRepo": { - "message": "檢視 Python 儲存庫" + "eventManagementDesc": { + "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "phpSDKRepo": { - "message": "檢視 PHP 儲存庫" + "deviceManagement": { + "message": "Device management" }, - "rubySDKRepo": { - "message": "檢視 Ruby 儲存庫" + "deviceManagementDesc": { + "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, - "goSDKRepo": { - "message": "檢視 Go 儲存庫" + "integrationCardTooltip": { + "message": "Launch $INTEGRATION$ implementation guide.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smIntegrationTooltip": { + "message": "Set up $INTEGRATION$.", + "placeholders": { + "integration": { + "content": "$1", + "example": "Google" + } + } + }, + "smSdkTooltip": { + "message": "View $SDK$ repository", + "placeholders": { + "sdk": { + "content": "$1", + "example": "Rust" + } + } + }, + "integrationCardAriaLabel": { + "message": "open $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } + }, + "smSdkAriaLabel": { + "message": "view $SDK$ repository in a new tab.", + "placeholders": { + "sdk": { + "content": "$1", + "example": "rust" + } + } + }, + "smIntegrationCardAriaLabel": { + "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "placeholders": { + "integration": { + "content": "$1", + "example": "google" + } + } }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." @@ -9014,6 +9270,12 @@ "providerPlan": { "message": "代管服務供應商" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "組織席位" }, @@ -9059,6 +9321,18 @@ "updatedTaxInformation": { "message": "已更新稅務資訊" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "未驗證" }, @@ -9416,6 +9690,30 @@ "additionalStorageGbMessage": { "message": "GB additional storage" }, + "sshKeyAlgorithm": { + "message": "Key algorithm" + }, + "sshKeyFingerprint": { + "message": "Fingerprint" + }, + "sshKeyPrivateKey": { + "message": "Private key" + }, + "sshKeyPublicKey": { + "message": "Public key" + }, + "sshKeyAlgorithmED25519": { + "message": "ED25519" + }, + "sshKeyAlgorithmRSA2048": { + "message": "RSA 2048-Bit" + }, + "sshKeyAlgorithmRSA3072": { + "message": "RSA 3072-Bit" + }, + "sshKeyAlgorithmRSA4096": { + "message": "RSA 4096-Bit" + }, "premiumAccounts": { "message": "6 premium accounts" }, @@ -9549,8 +9847,8 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "verified-domain-single-org-warning": { - "message": "Verifying a domain will turn on the single organization policy." + "claim-domain-single-org-warning": { + "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." @@ -9565,9 +9863,19 @@ "description": "Title for the delete organization user dialog" } }, - "deleteOrganizationUserWarning": { - "message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.", - "description": "Warning for the delete organization user dialog" + "deleteOrganizationUserWarningDesc": { + "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "description": "Warning description for the delete organization user dialog", + "placeholders": { + "name": { + "content": "$1", + "example": "John Doe" + } + } + }, + "deleteManyOrganizationUsersWarningDesc": { + "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { "message": "Deleted $NAME$", @@ -9580,5 +9888,253 @@ }, "organizationUserDeletedDesc": { "message": "The user was removed from the organization and all associated user data has been deleted." + }, + "deletedUserId": { + "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "userLeftOrganization": { + "message": "User $ID$ left organization", + "placeholders": { + "id": { + "content": "$1", + "example": "First 8 Character of a GUID" + } + } + }, + "suspendedOrganizationTitle": { + "message": "The $ORGANIZATION$ is suspended", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme c" + } + } + }, + "suspendedUserOrgMessage": { + "message": "Contact your organization owner for assistance." + }, + "suspendedOwnerOrgMessage": { + "message": "To regain access to your organization, add a payment method." + }, + "deleteMembers": { + "message": "Delete members" + }, + "noSelectedMembersApplicable": { + "message": "This action is not applicable to any of the selected members." + }, + "deletedSuccessfully": { + "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." + }, + "verifyBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifyBankAccountWithStatementDescriptorInstructions": { + "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "descriptorCode": { + "message": "Descriptor code" + }, + "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" + }, + "removeMembers": { + "message": "Remove members" + }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, + "claimedDomains": { + "message": "Claimed domains" + }, + "claimDomain": { + "message": "Claim domain" + }, + "reclaimDomain": { + "message": "Reclaim domain" + }, + "claimDomainNameInputHint": { + "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + }, + "automaticClaimedDomains": { + "message": "Automatic Claimed Domains" + }, + "automaticDomainClaimProcess": { + "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + }, + "domainNotClaimed": { + "message": "$DOMAIN$ not claimed. Check your DNS records.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainStatusClaimed": { + "message": "Claimed" + }, + "domainStatusUnderVerification": { + "message": "Under verification" + }, + "claimedDomainsDesc": { + "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + }, + "invalidDomainNameClaimMessage": { + "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + }, + "domainClaimedEvent": { + "message": "$DOMAIN$ claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "domainNotClaimedEvent": { + "message": "$DOMAIN$ not claimed", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForSentSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + } + } + }, + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "placeholders": { + "email": { + "content": "$1", + "example": "sponsored@organization.com" + }, + "date": { + "content": "$2", + "example": "12/10/2024" + } + } + }, + "domainClaimed": { + "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/scss/base.scss b/apps/web/src/scss/base.scss index 257662fd5b2..495e7df6926 100644 --- a/apps/web/src/scss/base.scss +++ b/apps/web/src/scss/base.scss @@ -1,5 +1,5 @@ html { - font-size: 14px; + font-size: 16px; } body { diff --git a/apps/web/src/scss/forms.scss b/apps/web/src/scss/forms.scss index f5800ff7e50..330bc5f6547 100644 --- a/apps/web/src/scss/forms.scss +++ b/apps/web/src/scss/forms.scss @@ -20,7 +20,7 @@ input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: -cancel-button; } -label:not(.form-check-label):not(.btn), +label:not(.form-check-label):not(.btn):not(:has(bit-label)), label.bold { font-weight: 600; @include themify($themes) { @@ -136,12 +136,12 @@ input[type="checkbox"] { background-color: rgb(var(--color-background)); &:hover { - border-color: rgb(var(--color-primary-500)); + border-color: rgb(var(--color-primary-600)); } &.is-focused { outline: 0; - border-color: rgb(var(--color-primary-500)); + border-color: rgb(var(--color-primary-600)); } &.is-invalid { diff --git a/apps/web/src/scss/plugins.scss b/apps/web/src/scss/plugins.scss index f4aa428532c..5fbd32ac4ee 100644 --- a/apps/web/src/scss/plugins.scss +++ b/apps/web/src/scss/plugins.scss @@ -12,7 +12,7 @@ } #web-authn-frame { - height: 290px; + height: 315px; @include themify($themes) { background: themed("imgLoading") 0 0 no-repeat; } diff --git a/apps/web/src/scss/variables.scss b/apps/web/src/scss/variables.scss index 9d3d8d6ad49..b3a27a7824b 100644 --- a/apps/web/src/scss/variables.scss +++ b/apps/web/src/scss/variables.scss @@ -20,7 +20,7 @@ $theme-colors: ( $body-bg: $white; $body-color: #333333; -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, +$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; $h1-font-size: 1.7rem; diff --git a/apps/web/src/theme.ts b/apps/web/src/theme.ts index 57193fadee4..870e30c80e0 100644 --- a/apps/web/src/theme.ts +++ b/apps/web/src/theme.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore // Set theme on page load // This is done outside the Angular app to avoid a flash of unthemed content before it loads const setTheme = () => { diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index 9d3c25d5cc7..a762053da35 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -7,13 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types -export type Flags = { - showPasswordless?: boolean; -} & SharedFlags; +export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 3ae0778250c..2c0108ca3e2 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -5,6 +5,7 @@ config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", + "../../libs/key-management/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../bitwarden_license/bit-web/src/**/*.{html,ts}", diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 3799945ea98..678db7c4af5 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -25,6 +25,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index df325015aad..9373308c112 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -256,7 +256,7 @@ const devServer = 'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4=' 'sha256-or0p3LaHetJ4FRq+flVORVFFNsOjQGWrDvX8Jf7ACWg=' 'sha256-jvLh2uL2/Pq/gpvNJMaEL4C+TNhBeGadLIUyPcVRZvY=' - 'sha256-Oca9ZYU1dwNscIhdNV7tFBsr4oqagBhZx9/p4w8GOcg=' + 'sha256-VZTcMoTEw3nbAHejvqlyyRm1Mdx+DVNgyKANjpWw0qg=' ;img-src 'self' data: diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts index bb00c50ab12..db63a3c1c68 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts index dc6a6d8e906..1c51f9397c5 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -1,10 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class ApproveCommand { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts index db73773f086..767acea99f7 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -1,11 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { MessageResponse } from "@bitwarden/cli/models/response/message.response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyAllCommand { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts index 58c1c576433..87e633b2bee 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -1,10 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyCommand { diff --git a/bitwarden_license/bit-cli/src/service-container.ts b/bitwarden_license/bit-cli/src/service-container.ts index f82efecdceb..9e659c1a59d 100644 --- a/bitwarden_license/bit-cli/src/service-container.ts +++ b/bitwarden_license/bit-cli/src/service-container.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationAuthRequestService, OrganizationAuthRequestApiService, diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index 9440a03375a..92a206f44db 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -24,12 +24,18 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/node/*": ["../../libs/node/src/*"], "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"] - } + }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ] }, "include": ["src", "src/**/*.spec.ts"] } diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index b2c4c1c04f9..4c4507e5cb8 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserApiService, OrganizationUserResetPasswordDetailsResponse, diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts index d9f36013521..d32d6fcfbc7 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "@bitwarden/common/models/view/view"; import { PendingOrganizationAuthRequestResponse } from "."; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts new file mode 100644 index 00000000000..94dad65fdc9 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -0,0 +1,111 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore + +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { BadgeVariant } from "@bitwarden/components"; + +/** + * All applications report summary. The total members, + * total at risk members, application, and at risk application + * counts. Aggregated from all calculated applications + */ +export type ApplicationHealthReportSummary = { + totalMemberCount: number; + totalAtRiskMemberCount: number; + totalApplicationCount: number; + totalAtRiskApplicationCount: number; +}; + +/** + * All applications report detail. Application is the cipher + * uri. Has the at risk, password, and member information + */ +export type ApplicationHealthReportDetail = { + applicationName: string; + passwordCount: number; + atRiskPasswordCount: number; + memberCount: number; + atRiskMemberCount: number; + memberDetails: MemberDetailsFlat[]; + atRiskMemberDetails: MemberDetailsFlat[]; +}; + +/** + * Breaks the cipher health info out by uri and passes + * along the password health and member info + */ +export type CipherHealthReportUriDetail = { + cipherId: string; + reusedPasswordCount: number; + weakPasswordDetail: WeakPasswordDetail; + exposedPasswordDetail: ExposedPasswordDetail; + cipherMembers: MemberDetailsFlat[]; + trimmedUri: string; +}; + +/** + * Associates a cipher with it's essential information. + * Gets the password health details, cipher members, and + * the trimmed uris for the cipher + */ +export type CipherHealthReportDetail = CipherView & { + reusedPasswordCount: number; + weakPasswordDetail: WeakPasswordDetail; + exposedPasswordDetail: ExposedPasswordDetail; + cipherMembers: MemberDetailsFlat[]; + trimmedUris: string[]; +}; + +/** + * Weak password details containing the score + * and the score type for the label and badge + */ +export type WeakPasswordDetail = { + score: number; + detailValue: WeakPasswordScore; +} | null; + +/** + * Weak password details containing the badge and + * the label for the password score + */ +export type WeakPasswordScore = { + label: string; + badgeVariant: BadgeVariant; +} | null; + +/** + * How many times a password has been exposed + */ +export type ExposedPasswordDetail = { + exposedXTimes: number; +} | null; + +/** + * Flattened member details that associates an + * organization member to a cipher + */ +export type MemberDetailsFlat = { + userName: string; + email: string; + cipherId: string; +}; + +/** + * Member email with the number of at risk passwords + * At risk member detail that contains the email + * and the count of at risk ciphers + */ +export type AtRiskMemberDetail = { + email: string; + atRiskPasswordCount: number; +}; + +/* + * A list of applications and the count of + * at risk passwords for each application + */ +export type AtRiskApplicationDetail = { + applicationName: string; + atRiskPasswordCount: number; +}; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts index e7693e46a32..ca5cdc35b8a 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/ciphers.mock.ts @@ -1,10 +1,18 @@ +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; + +const createLoginUriView = (uri: string): LoginUriView => { + const view = new LoginUriView(); + view.uri = uri; + return view; +}; + export const mockCiphers: any[] = [ { initializerKey: 1, id: "cbea34a8-bde4-46ad-9d19-b05001228ab1", organizationId: null, folderId: null, - name: "Cannot Be Edited", + name: "Weak Password Cipher", notes: null, isDeleted: false, type: 1, @@ -14,10 +22,11 @@ export const mockCiphers: any[] = [ password: "123", hasUris: true, uris: [ - { uri: "www.google.com" }, - { uri: "accounts.google.com" }, - { uri: "https://www.google.com" }, - { uri: "https://www.google.com/login" }, + createLoginUriView("101domain.com"), + createLoginUriView("www.google.com"), + createLoginUriView("accounts.google.com"), + createLoginUriView("https://www.google.com"), + createLoginUriView("https://www.google.com/login"), ], }, edit: false, @@ -31,23 +40,18 @@ export const mockCiphers: any[] = [ }, { initializerKey: 1, - id: "cbea34a8-bde4-46ad-9d19-b05001228ab2", + id: "cbea34a8-bde4-46ad-9d19-b05001228cd3", organizationId: null, folderId: null, - name: "Can Be Edited id ending 2", + name: "Strong Password Cipher", notes: null, - isDeleted: false, type: 1, favorite: false, organizationUseTotp: false, login: { - password: "123", + password: "Password!123", hasUris: true, - uris: [ - { - uri: "http://nothing.com", - }, - ], + uris: [createLoginUriView("http://example.com")], }, edit: true, viewPassword: true, @@ -60,22 +64,18 @@ export const mockCiphers: any[] = [ }, { initializerKey: 1, - id: "cbea34a8-bde4-46ad-9d19-b05001228cd3", + id: "cbea34a8-bde4-46ad-9d19-b05001228ab2", organizationId: null, folderId: null, - name: "Can Be Edited id ending 3", + name: "Strong password Cipher", notes: null, type: 1, favorite: false, organizationUseTotp: false, login: { - password: "123", hasUris: true, - uris: [ - { - uri: "http://example.com", - }, - ], + password: "Password!1234", + uris: [createLoginUriView("101domain.com")], }, edit: true, viewPassword: true, @@ -91,14 +91,15 @@ export const mockCiphers: any[] = [ id: "cbea34a8-bde4-46ad-9d19-b05001228xy4", organizationId: null, folderId: null, - name: "Can Be Edited id ending 4", + name: "Strong password Cipher", notes: null, type: 1, favorite: false, organizationUseTotp: false, login: { hasUris: true, - uris: [{ uri: "101domain.com" }], + password: "Password!123", + uris: [createLoginUriView("example.com")], }, edit: true, viewPassword: true, @@ -114,14 +115,39 @@ export const mockCiphers: any[] = [ id: "cbea34a8-bde4-46ad-9d19-b05001227nm5", organizationId: null, folderId: null, - name: "Can Be Edited id ending 5", + name: "Exposed password Cipher", + notes: null, + type: 1, + favorite: false, + organizationUseTotp: false, + login: { + hasUris: true, + password: "123", + uris: [createLoginUriView("123formbuilder.com"), createLoginUriView("www.google.com")], + }, + edit: true, + viewPassword: true, + collectionIds: [], + revisionDate: "2023-08-03T17:40:59.793Z", + creationDate: "2023-08-03T17:40:59.793Z", + deletedDate: null, + reprompt: 0, + localData: null, + }, + { + initializerKey: 1, + id: "cbea34a8-bde4-46ad-9d19-b05001227tt1", + organizationId: null, + folderId: null, + name: "Secure Co Login", notes: null, type: 1, favorite: false, organizationUseTotp: false, login: { hasUris: true, - uris: [{ uri: "123formbuilder.com" }], + password: "4gRyhhOX2Og2p0", + uris: [createLoginUriView("SecureCo.com")], }, edit: true, viewPassword: true, diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts index c7bace84e5b..a8e62437b9d 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts @@ -1,2 +1,4 @@ export * from "./member-cipher-details-api.service"; export * from "./password-health.service"; +export * from "./risk-insights-report.service"; +export * from "./risk-insights-data.service"; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts index b71abe075e6..d6474c2c9c4 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts @@ -4,74 +4,78 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; -const mockMemberCipherDetails: any = { - data: [ - { - userName: "David Brent", - email: "david.brent@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab1", - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - ], - }, - { - userName: "Tim Canterbury", - email: "tim.canterbury@wernhamhogg.uk", - usesKeyConnector: false, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - ], - }, - { - userName: "Gareth Keenan", - email: "gareth.keenan@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - "cbea34a8-bde4-46ad-9d19-b05001227nm7", - ], - }, - { - userName: "Dawn Tinsley", - email: "dawn.tinsley@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - ], - }, - { - userName: "Keith Bishop", - email: "keith.bishop@wernhamhogg.uk", - usesKeyConnector: false, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab1", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - ], - }, - { - userName: "Chris Finch", - email: "chris.finch@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - ], - }, - ], -}; +export const mockMemberCipherDetails: any = [ + { + userName: "David Brent", + email: "david.brent@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Tim Canterbury", + email: "tim.canterbury@wernhamhogg.uk", + usesKeyConnector: false, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Gareth Keenan", + email: "gareth.keenan@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + "cbea34a8-bde4-46ad-9d19-b05001227nm7", + ], + }, + { + userName: "Dawn Tinsley", + email: "dawn.tinsley@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + ], + }, + { + userName: "Keith Bishop", + email: "keith.bishop@wernhamhogg.uk", + usesKeyConnector: false, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Chris Finch", + email: "chris.finch@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + ], + }, + { + userName: "Mister Secure", + email: "mister.secure@secureco.com", + usesKeyConnector: true, + cipherIds: ["cbea34a8-bde4-46ad-9d19-b05001227tt1"], + }, +]; describe("Member Cipher Details API Service", () => { let memberCipherDetailsApiService: MemberCipherDetailsApiService; @@ -93,7 +97,7 @@ describe("Member Cipher Details API Service", () => { const orgId = "1234"; const result = await memberCipherDetailsApiService.getMemberCipherDetails(orgId); expect(result).not.toBeNull(); - expect(result).toHaveLength(6); + expect(result).toHaveLength(7); expect(apiService.send).toHaveBeenCalledWith( "GET", "/reports/member-cipher-details/" + orgId, diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts index 9351ac87776..b38f8712add 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts @@ -1,8 +1,10 @@ +import { Injectable } from "@angular/core"; + import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response"; +@Injectable() export class MemberCipherDetailsApiService { constructor(private apiService: ApiService) {} @@ -21,7 +23,6 @@ export class MemberCipherDetailsApiService { true, ); - const listResponse = new ListResponse(response, MemberCipherDetailsResponse); - return listResponse.data.map((r) => new MemberCipherDetailsResponse(r)); + return response; } } diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts index 692eb5afba7..b81acb09bed 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts @@ -3,16 +3,15 @@ import { TestBed } from "@angular/core/testing"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { mockCiphers } from "./ciphers.mock"; +import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; +import { mockMemberCipherDetails } from "./member-cipher-details-api.service.spec"; import { PasswordHealthService } from "./password-health.service"; +// FIXME: Remove password-health report service after PR-15498 completion describe("PasswordHealthService", () => { let service: PasswordHealthService; - let cipherService: CipherService; - beforeEach(() => { TestBed.configureTestingModule({ providers: [ @@ -35,7 +34,13 @@ describe("PasswordHealthService", () => { { provide: CipherService, useValue: { - getAllFromApiForOrganization: jest.fn().mockResolvedValue(CipherData), + getAllFromApiForOrganization: jest.fn().mockResolvedValue(mockCiphers), + }, + }, + { + provide: MemberCipherDetailsApiService, + useValue: { + getMemberCipherDetails: jest.fn().mockResolvedValue(mockMemberCipherDetails), }, }, { provide: "organizationId", useValue: "org1" }, @@ -43,7 +48,6 @@ describe("PasswordHealthService", () => { }); service = TestBed.inject(PasswordHealthService); - cipherService = TestBed.inject(CipherService); }); it("should be created", () => { @@ -58,79 +62,4 @@ describe("PasswordHealthService", () => { expect(service.exposedPasswordMap.size).toBe(0); expect(service.totalMembersMap.size).toBe(0); }); - - describe("generateReport", () => { - beforeEach(async () => { - await service.generateReport(); - }); - - it("should fetch all ciphers for the organization", () => { - expect(cipherService.getAllFromApiForOrganization).toHaveBeenCalledWith("org1"); - }); - - it("should populate reportCiphers with ciphers that have issues", () => { - expect(service.reportCiphers.length).toBeGreaterThan(0); - }); - - it("should detect weak passwords", () => { - expect(service.passwordStrengthMap.size).toBeGreaterThan(0); - expect(service.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toEqual([ - "veryWeak", - "danger", - ]); - expect(service.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toEqual([ - "veryWeak", - "danger", - ]); - expect(service.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toEqual([ - "veryWeak", - "danger", - ]); - }); - - it("should detect reused passwords", () => { - expect(service.passwordUseMap.get("123")).toBe(3); - }); - - it("should detect exposed passwords", () => { - expect(service.exposedPasswordMap.size).toBeGreaterThan(0); - expect(service.exposedPasswordMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(100); - }); - - it("should calculate total members per cipher", () => { - expect(service.totalMembersMap.size).toBeGreaterThan(0); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(3); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(5); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(6); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm5")).toBe(4); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm7")).toBe(1); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(7); - }); - }); - - describe("findWeakPassword", () => { - it("should add weak passwords to passwordStrengthMap", () => { - const weakCipher = mockCiphers.find((c) => c.login?.password === "123") as CipherView; - service.findWeakPassword(weakCipher); - expect(service.passwordStrengthMap.get(weakCipher.id)).toEqual(["veryWeak", "danger"]); - }); - }); - - describe("findReusedPassword", () => { - it("should detect password reuse", () => { - mockCiphers.forEach((cipher) => { - service.findReusedPassword(cipher as CipherView); - }); - const reuseCounts = Array.from(service.passwordUseMap.values()).filter((count) => count > 1); - expect(reuseCounts.length).toBeGreaterThan(0); - }); - }); - - describe("findExposedPassword", () => { - it("should add exposed passwords to exposedPasswordMap", async () => { - const exposedCipher = mockCiphers.find((c) => c.login?.password === "123") as CipherView; - await service.findExposedPassword(exposedCipher); - expect(service.exposedPasswordMap.get(exposedCipher.id)).toBe(100); - }); - }); }); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts index 1709261922b..f2f9a9868f7 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts @@ -1,9 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Inject, Injectable } from "@angular/core"; -// eslint-disable-next-line no-restricted-imports -import { mockCiphers } from "@bitwarden/bit-common/tools/reports/risk-insights/services/ciphers.mock"; -// eslint-disable-next-line no-restricted-imports -import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/risk-insights/services/member-cipher-details-response.mock"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -12,6 +10,8 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeVariant } from "@bitwarden/components"; +import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; + @Injectable() export class PasswordHealthService { reportCiphers: CipherView[] = []; @@ -30,21 +30,23 @@ export class PasswordHealthService { private passwordStrengthService: PasswordStrengthServiceAbstraction, private auditService: AuditService, private cipherService: CipherService, + private memberCipherDetailsApiService: MemberCipherDetailsApiService, @Inject("organizationId") private organizationId: string, ) {} async generateReport() { - let allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organizationId); - // TODO remove when actual user member data is available - allCiphers = mockCiphers; + const allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organizationId); allCiphers.forEach(async (cipher) => { this.findWeakPassword(cipher); this.findReusedPassword(cipher); await this.findExposedPassword(cipher); }); - // TODO - fetch actual user member when data is available - mockMemberCipherDetailsResponse.data.forEach((user) => { + const memberCipherDetails = await this.memberCipherDetailsApiService.getMemberCipherDetails( + this.organizationId, + ); + + memberCipherDetails.forEach((user) => { user.cipherIds.forEach((cipherId: string) => { if (this.totalMembersMap.has(cipherId)) { this.totalMembersMap.set(cipherId, (this.totalMembersMap.get(cipherId) || 0) + 1); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts new file mode 100644 index 00000000000..42bab69fca4 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts @@ -0,0 +1,60 @@ +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { ApplicationHealthReportDetail } from "../models/password-health"; + +import { RiskInsightsReportService } from "./risk-insights-report.service"; + +export class RiskInsightsDataService { + private applicationsSubject = new BehaviorSubject(null); + + applications$ = this.applicationsSubject.asObservable(); + + private isLoadingSubject = new BehaviorSubject(false); + isLoading$ = this.isLoadingSubject.asObservable(); + + private isRefreshingSubject = new BehaviorSubject(false); + isRefreshing$ = this.isRefreshingSubject.asObservable(); + + private errorSubject = new BehaviorSubject(null); + error$ = this.errorSubject.asObservable(); + + private dataLastUpdatedSubject = new BehaviorSubject(null); + dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable(); + + constructor(private reportService: RiskInsightsReportService) {} + + /** + * Fetches the applications report and updates the applicationsSubject. + * @param organizationId The ID of the organization. + */ + fetchApplicationsReport(organizationId: string, isRefresh?: boolean): void { + if (isRefresh) { + this.isRefreshingSubject.next(true); + } else { + this.isLoadingSubject.next(true); + } + this.reportService + .generateApplicationsReport$(organizationId) + .pipe( + finalize(() => { + this.isLoadingSubject.next(false); + this.isRefreshingSubject.next(false); + this.dataLastUpdatedSubject.next(new Date()); + }), + ) + .subscribe({ + next: (reports: ApplicationHealthReportDetail[]) => { + this.applicationsSubject.next(reports); + this.errorSubject.next(null); + }, + error: () => { + this.applicationsSubject.next([]); + }, + }); + } + + refreshApplicationsReport(organizationId: string): void { + this.fetchApplicationsReport(organizationId, true); + } +} diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts new file mode 100644 index 00000000000..705eb1231a9 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.spec.ts @@ -0,0 +1,138 @@ +import { mock } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; +import { ZXCVBNResult } from "zxcvbn"; + +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; + +import { mockCiphers } from "./ciphers.mock"; +import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; +import { mockMemberCipherDetails } from "./member-cipher-details-api.service.spec"; +import { RiskInsightsReportService } from "./risk-insights-report.service"; + +describe("RiskInsightsReportService", () => { + let service: RiskInsightsReportService; + const pwdStrengthService = mock(); + const auditService = mock(); + const cipherService = mock(); + const memberCipherDetailsService = mock(); + + beforeEach(() => { + pwdStrengthService.getPasswordStrength.mockImplementation((password: string) => { + const score = password.length < 4 ? 1 : 4; + return { score } as ZXCVBNResult; + }); + + auditService.passwordLeaked.mockImplementation((password: string) => + Promise.resolve(password === "123" ? 100 : 0), + ); + + cipherService.getAllFromApiForOrganization.mockResolvedValue(mockCiphers); + + memberCipherDetailsService.getMemberCipherDetails.mockResolvedValue(mockMemberCipherDetails); + + service = new RiskInsightsReportService( + pwdStrengthService, + auditService, + cipherService, + memberCipherDetailsService, + ); + }); + + it("should generate the raw data report correctly", async () => { + const result = await firstValueFrom(service.generateRawDataReport$("orgId")); + + expect(result).toHaveLength(6); + + let testCaseResults = result.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001228ab1"); + expect(testCaseResults).toHaveLength(1); + let testCase = testCaseResults[0]; + expect(testCase).toBeTruthy(); + expect(testCase.cipherMembers).toHaveLength(2); + expect(testCase.trimmedUris).toHaveLength(3); + expect(testCase.weakPasswordDetail).toBeTruthy(); + expect(testCase.exposedPasswordDetail).toBeTruthy(); + expect(testCase.reusedPasswordCount).toEqual(2); + + testCaseResults = result.filter((x) => x.id === "cbea34a8-bde4-46ad-9d19-b05001227tt1"); + expect(testCaseResults).toHaveLength(1); + testCase = testCaseResults[0]; + expect(testCase).toBeTruthy(); + expect(testCase.cipherMembers).toHaveLength(1); + expect(testCase.trimmedUris).toHaveLength(1); + expect(testCase.weakPasswordDetail).toBeFalsy(); + expect(testCase.exposedPasswordDetail).toBeFalsy(); + expect(testCase.reusedPasswordCount).toEqual(1); + }); + + it("should generate the raw data + uri report correctly", async () => { + const result = await firstValueFrom(service.generateRawDataUriReport$("orgId")); + + expect(result).toHaveLength(9); + + // Two ciphers that have google.com as their uri. There should be 2 results + const googleResults = result.filter((x) => x.trimmedUri === "google.com"); + expect(googleResults).toHaveLength(2); + + // Verify the details for one of the googles matches the password health info + // expected + const firstGoogle = googleResults.filter( + (x) => x.cipherId === "cbea34a8-bde4-46ad-9d19-b05001228ab1" && x.trimmedUri === "google.com", + )[0]; + expect(firstGoogle.weakPasswordDetail).toBeTruthy(); + expect(firstGoogle.exposedPasswordDetail).toBeTruthy(); + expect(firstGoogle.reusedPasswordCount).toEqual(2); + }); + + it("should generate applications health report data correctly", async () => { + const result = await firstValueFrom(service.generateApplicationsReport$("orgId")); + + expect(result).toHaveLength(6); + + // Two ciphers have google.com associated with them. The first cipher + // has 2 members and the second has 4. However, the 2 members in the first + // cipher are also associated with the second. The total amount of members + // should be 4 not 6 + const googleTestResults = result.filter((x) => x.applicationName === "google.com"); + expect(googleTestResults).toHaveLength(1); + const googleTest = googleTestResults[0]; + expect(googleTest.memberCount).toEqual(4); + + // Both ciphers have at risk passwords + expect(googleTest.passwordCount).toEqual(2); + + // All members are at risk since both ciphers are at risk + expect(googleTest.atRiskMemberDetails).toHaveLength(4); + expect(googleTest.atRiskPasswordCount).toEqual(2); + + // There are 2 ciphers associated with 101domain.com + const domain101TestResults = result.filter((x) => x.applicationName === "101domain.com"); + expect(domain101TestResults).toHaveLength(1); + const domain101Test = domain101TestResults[0]; + expect(domain101Test.passwordCount).toEqual(2); + + // The first cipher is at risk. The second cipher is not at risk + expect(domain101Test.atRiskPasswordCount).toEqual(1); + + // The first cipher has 2 members. The second cipher the second + // cipher has 4. One of the members in the first cipher is associated + // with the second. So there should be 5 members total. + expect(domain101Test.memberCount).toEqual(5); + + // The first cipher is at risk. The total at risk members is 2 and + // at risk password count is 1. + expect(domain101Test.atRiskMemberDetails).toHaveLength(2); + expect(domain101Test.atRiskPasswordCount).toEqual(1); + }); + + it("should generate applications summary data correctly", async () => { + const reportResult = await firstValueFrom(service.generateApplicationsReport$("orgId")); + const reportSummary = service.generateApplicationsSummary(reportResult); + + expect(reportSummary.totalMemberCount).toEqual(7); + expect(reportSummary.totalAtRiskMemberCount).toEqual(6); + expect(reportSummary.totalApplicationCount).toEqual(6); + expect(reportSummary.totalAtRiskApplicationCount).toEqual(5); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts new file mode 100644 index 00000000000..c3bcc59eca5 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -0,0 +1,444 @@ +// FIXME: Update this file to be type safe +// @ts-strict-ignore +import { concatMap, first, from, map, Observable, zip } from "rxjs"; + +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { + ApplicationHealthReportDetail, + ApplicationHealthReportSummary, + AtRiskMemberDetail, + AtRiskApplicationDetail, + CipherHealthReportDetail, + CipherHealthReportUriDetail, + ExposedPasswordDetail, + MemberDetailsFlat, + WeakPasswordDetail, + WeakPasswordScore, +} from "../models/password-health"; + +import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; + +export class RiskInsightsReportService { + constructor( + private passwordStrengthService: PasswordStrengthServiceAbstraction, + private auditService: AuditService, + private cipherService: CipherService, + private memberCipherDetailsApiService: MemberCipherDetailsApiService, + ) {} + + /** + * Report data from raw cipher health data. + * Can be used in the Raw Data diagnostic tab (just exclude the members in the view) + * and can be used in the raw data + members tab when including the members in the view + * @param organizationId + * @returns Cipher health report data with members and trimmed uris + */ + generateRawDataReport$(organizationId: string): Observable { + const allCiphers$ = from(this.cipherService.getAllFromApiForOrganization(organizationId)); + const memberCiphers$ = from( + this.memberCipherDetailsApiService.getMemberCipherDetails(organizationId), + ); + + const results$ = zip(allCiphers$, memberCiphers$).pipe( + map(([allCiphers, memberCiphers]) => { + const details: MemberDetailsFlat[] = memberCiphers.flatMap((dtl) => + dtl.cipherIds.map((c) => this.getMemberDetailsFlat(dtl.userName, dtl.email, c)), + ); + return [allCiphers, details] as const; + }), + concatMap(([ciphers, flattenedDetails]) => this.getCipherDetails(ciphers, flattenedDetails)), + first(), + ); + + return results$; + } + + /** + * Report data for raw cipher health broken out into the uris + * Can be used in the raw data + members + uri diagnostic report + * @param organizationId Id of the organization + * @returns Cipher health report data flattened to the uris + */ + generateRawDataUriReport$(organizationId: string): Observable { + const cipherHealthDetails$ = this.generateRawDataReport$(organizationId); + const results$ = cipherHealthDetails$.pipe( + map((healthDetails) => this.getCipherUriDetails(healthDetails)), + first(), + ); + + return results$; + } + + /** + * Report data for the aggregation of uris to like uris and getting password/member counts, + * members, and at risk statuses. + * @param organizationId Id of the organization + * @returns The all applications health report data + */ + generateApplicationsReport$(organizationId: string): Observable { + const cipherHealthUriReport$ = this.generateRawDataUriReport$(organizationId); + const results$ = cipherHealthUriReport$.pipe( + map((uriDetails) => this.getApplicationHealthReport(uriDetails)), + first(), + ); + + return results$; + } + + /** + * Generates a list of members with at-risk passwords along with the number of at-risk passwords. + */ + generateAtRiskMemberList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskMemberDetail[] { + const memberRiskMap = new Map(); + + cipherHealthReportDetails.forEach((app) => { + app.atRiskMemberDetails.forEach((member) => { + if (memberRiskMap.has(member.email)) { + memberRiskMap.set(member.email, memberRiskMap.get(member.email) + 1); + } else { + memberRiskMap.set(member.email, 1); + } + }); + }); + + return Array.from(memberRiskMap.entries()).map(([email, atRiskPasswordCount]) => ({ + email, + atRiskPasswordCount, + })); + } + + generateAtRiskApplicationList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskApplicationDetail[] { + const appsRiskMap = new Map(); + + cipherHealthReportDetails + .filter((app) => app.atRiskPasswordCount > 0) + .forEach((app) => { + if (appsRiskMap.has(app.applicationName)) { + appsRiskMap.set( + app.applicationName, + appsRiskMap.get(app.applicationName) + app.atRiskPasswordCount, + ); + } else { + appsRiskMap.set(app.applicationName, app.atRiskPasswordCount); + } + }); + + return Array.from(appsRiskMap.entries()).map(([applicationName, atRiskPasswordCount]) => ({ + applicationName, + atRiskPasswordCount, + })); + } + + /** + * Gets the summary from the application health report. Returns total members and applications as well + * as the total at risk members and at risk applications + * @param reports The previously calculated application health report data + * @returns A summary object containing report totals + */ + generateApplicationsSummary( + reports: ApplicationHealthReportDetail[], + ): ApplicationHealthReportSummary { + const totalMembers = reports.flatMap((x) => x.memberDetails); + const uniqueMembers = this.getUniqueMembers(totalMembers); + + const atRiskMembers = reports.flatMap((x) => x.atRiskMemberDetails); + const uniqueAtRiskMembers = this.getUniqueMembers(atRiskMembers); + + return { + totalMemberCount: uniqueMembers.length, + totalAtRiskMemberCount: uniqueAtRiskMembers.length, + totalApplicationCount: reports.length, + totalAtRiskApplicationCount: reports.filter((app) => app.atRiskPasswordCount > 0).length, + }; + } + + /** + * Associates the members with the ciphers they have access to. Calculates the password health. + * Finds the trimmed uris. + * @param ciphers Org ciphers + * @param memberDetails Org members + * @returns Cipher password health data with trimmed uris and associated members + */ + private async getCipherDetails( + ciphers: CipherView[], + memberDetails: MemberDetailsFlat[], + ): Promise { + const cipherHealthReports: CipherHealthReportDetail[] = []; + const passwordUseMap = new Map(); + for (const cipher of ciphers) { + if (this.validateCipher(cipher)) { + const weakPassword = this.findWeakPassword(cipher); + // Looping over all ciphers needs to happen first to determine reused passwords over all ciphers. + // Store in the set and evaluate later + if (passwordUseMap.has(cipher.login.password)) { + passwordUseMap.set( + cipher.login.password, + (passwordUseMap.get(cipher.login.password) || 0) + 1, + ); + } else { + passwordUseMap.set(cipher.login.password, 1); + } + + const exposedPassword = await this.findExposedPassword(cipher); + + // Get the cipher members + const cipherMembers = memberDetails.filter((x) => x.cipherId === cipher.id); + + // Trim uris to host name and create the cipher health report + const cipherTrimmedUris = this.getTrimmedCipherUris(cipher); + const cipherHealth = { + ...cipher, + weakPasswordDetail: weakPassword, + exposedPasswordDetail: exposedPassword, + cipherMembers: cipherMembers, + trimmedUris: cipherTrimmedUris, + } as CipherHealthReportDetail; + + cipherHealthReports.push(cipherHealth); + } + } + + // loop for reused passwords + cipherHealthReports.forEach((detail) => { + detail.reusedPasswordCount = passwordUseMap.get(detail.login.password) ?? 0; + }); + return cipherHealthReports; + } + + /** + * Flattens the cipher to trimmed uris. Used for the raw data + uri + * @param cipherHealthReport Cipher health report with uris and members + * @returns Flattened cipher health details to uri + */ + private getCipherUriDetails( + cipherHealthReport: CipherHealthReportDetail[], + ): CipherHealthReportUriDetail[] { + return cipherHealthReport.flatMap((rpt) => + rpt.trimmedUris.map((u) => this.getFlattenedCipherDetails(rpt, u)), + ); + } + + /** + * Loop through the flattened cipher to uri data. If the item exists it's values need to be updated with the new item. + * If the item is new, create and add the object with the flattened details + * @param cipherHealthUriReport Cipher and password health info broken out into their uris + * @returns Application health reports + */ + private getApplicationHealthReport( + cipherHealthUriReport: CipherHealthReportUriDetail[], + ): ApplicationHealthReportDetail[] { + const appReports: ApplicationHealthReportDetail[] = []; + cipherHealthUriReport.forEach((uri) => { + const index = appReports.findIndex((item) => item.applicationName === uri.trimmedUri); + + let atRisk: boolean = false; + if (uri.exposedPasswordDetail || uri.weakPasswordDetail || uri.reusedPasswordCount > 1) { + atRisk = true; + } + + if (index === -1) { + appReports.push(this.getApplicationReportDetail(uri, atRisk)); + } else { + appReports[index] = this.getApplicationReportDetail(uri, atRisk, appReports[index]); + } + }); + return appReports; + } + + private async findExposedPassword(cipher: CipherView): Promise { + const exposedCount = await this.auditService.passwordLeaked(cipher.login.password); + if (exposedCount > 0) { + const exposedDetail = { exposedXTimes: exposedCount } as ExposedPasswordDetail; + return exposedDetail; + } + return null; + } + + private findWeakPassword(cipher: CipherView): WeakPasswordDetail { + const hasUserName = this.isUserNameNotEmpty(cipher); + let userInput: string[] = []; + if (hasUserName) { + const atPosition = cipher.login.username.indexOf("@"); + if (atPosition > -1) { + userInput = userInput + .concat( + cipher.login.username + .substring(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/), + ) + .filter((i) => i.length >= 3); + } else { + userInput = cipher.login.username + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + .filter((i) => i.length >= 3); + } + } + const { score } = this.passwordStrengthService.getPasswordStrength( + cipher.login.password, + null, + userInput.length > 0 ? userInput : null, + ); + + if (score != null && score <= 2) { + const scoreValue = this.weakPasswordScore(score); + const weakPasswordDetail = { score: score, detailValue: scoreValue } as WeakPasswordDetail; + return weakPasswordDetail; + } + return null; + } + + private weakPasswordScore(score: number): WeakPasswordScore { + switch (score) { + case 4: + return { label: "strong", badgeVariant: "success" }; + case 3: + return { label: "good", badgeVariant: "primary" }; + case 2: + return { label: "weak", badgeVariant: "warning" }; + default: + return { label: "veryWeak", badgeVariant: "danger" }; + } + } + + /** + * Create the new application health report detail object with the details from the cipher health report uri detail object + * update or create the at risk values if the item is at risk. + * @param newUriDetail New cipher uri detail + * @param isAtRisk If the cipher has a weak, exposed, or reused password it is at risk + * @param existingUriDetail The previously processed Uri item + * @returns The new or updated application health report detail + */ + private getApplicationReportDetail( + newUriDetail: CipherHealthReportUriDetail, + isAtRisk: boolean, + existingUriDetail?: ApplicationHealthReportDetail, + ): ApplicationHealthReportDetail { + const reportDetail = { + applicationName: existingUriDetail + ? existingUriDetail.applicationName + : newUriDetail.trimmedUri, + passwordCount: existingUriDetail ? existingUriDetail.passwordCount + 1 : 1, + memberDetails: existingUriDetail + ? this.getUniqueMembers(existingUriDetail.memberDetails.concat(newUriDetail.cipherMembers)) + : newUriDetail.cipherMembers, + atRiskMemberDetails: existingUriDetail ? existingUriDetail.atRiskMemberDetails : [], + atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0, + atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0, + } as ApplicationHealthReportDetail; + + if (isAtRisk) { + reportDetail.atRiskPasswordCount = reportDetail.atRiskPasswordCount + 1; + reportDetail.atRiskMemberDetails = this.getUniqueMembers( + reportDetail.atRiskMemberDetails.concat(newUriDetail.cipherMembers), + ); + reportDetail.atRiskMemberCount = reportDetail.atRiskMemberDetails.length; + } + + reportDetail.memberCount = reportDetail.memberDetails.length; + + return reportDetail; + } + + /** + * Get a distinct array of members from a combined list. Input list may contain + * duplicate members. + * @param orgMembers Input list of members + * @returns Distinct array of members + */ + private getUniqueMembers(orgMembers: MemberDetailsFlat[]): MemberDetailsFlat[] { + const existingEmails = new Set(); + const distinctUsers = orgMembers.filter((member) => { + if (existingEmails.has(member.email)) { + return false; + } + existingEmails.add(member.email); + return true; + }); + return distinctUsers; + } + + private getFlattenedCipherDetails( + detail: CipherHealthReportDetail, + uri: string, + ): CipherHealthReportUriDetail { + return { + cipherId: detail.id, + reusedPasswordCount: detail.reusedPasswordCount, + weakPasswordDetail: detail.weakPasswordDetail, + exposedPasswordDetail: detail.exposedPasswordDetail, + cipherMembers: detail.cipherMembers, + trimmedUri: uri, + }; + } + + private getMemberDetailsFlat( + userName: string, + email: string, + cipherId: string, + ): MemberDetailsFlat { + return { + userName: userName, + email: email, + cipherId: cipherId, + }; + } + + /** + * Trim the cipher uris down to get the password health application. + * The uri should only exist once after being trimmed. No duplication. + * Example: + * - Untrimmed Uris: https://gmail.com, gmail.com/login + * - Both would trim to gmail.com + * - The cipher trimmed uri list should only return on instance in the list + * @param cipher + * @returns distinct list of trimmed cipher uris + */ + private getTrimmedCipherUris(cipher: CipherView): string[] { + const cipherUris: string[] = []; + const uris = cipher.login?.uris ?? []; + uris.map((u: { uri: string }) => { + const uri = Utils.getHostname(u.uri).replace("www.", ""); + if (!cipherUris.includes(uri)) { + cipherUris.push(uri); + } + }); + return cipherUris; + } + + private isUserNameNotEmpty(c: CipherView): boolean { + return !Utils.isNullOrWhitespace(c.login.username); + } + + /** + * Validates that the cipher is a login item, has a password + * is not deleted, and the user can view the password + * @param c the input cipher + */ + private validateCipher(c: CipherView): boolean { + const { type, login, isDeleted, viewPassword } = c; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + !viewPassword + ) { + return false; + } + return true; + } +} diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 03f3bd2d2f1..7791840950b 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../libs/shared/tsconfig.libs", + "extends": "../../libs/shared/tsconfig", "include": ["src", "spec"], "exclude": ["node_modules", "dist"], "compilerOptions": { @@ -23,6 +23,7 @@ "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["../../apps/web/src/*"], diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 4a4cbe312c2..ac8ad3112b9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject, Subject, switchMap, takeUntil, tap } from "rxjs"; @@ -96,6 +98,8 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { title: null, message: this.i18nService.t("loginRequestApproved"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.toastService.showToast({ variant: "error", diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html index 0d0ca04f926..7226c957598 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html @@ -6,24 +6,37 @@ {{ "newDomain" | i18n }} - {{ "verifyDomain" | i18n }} + + {{ + ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n + }} {{ data.orgDomain.domainName }} {{ - "domainStatusUnverified" | i18n + ((accountDeprovisioningEnabled$ | async) + ? "domainStatusUnderVerification" + : "domainStatusUnverified" + ) | i18n }} {{ - "domainStatusVerified" | i18n + ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified") + | i18n }}
{{ "domainName" | i18n }} - {{ "domainNameInputHint" | i18n }} + {{ + ((accountDeprovisioningEnabled$ | async) + ? "claimDomainNameInputHint" + : "domainNameInputHint" + ) | i18n + }} @@ -33,29 +46,38 @@ + > - {{ "automaticDomainVerificationProcess" | i18n }} + {{ + ((accountDeprovisioningEnabled$ | async) + ? "automaticDomainClaimProcess" + : "automaticDomainVerificationProcess" + ) | i18n + }}
+

+ {{ "claimedDomainsDesc" | i18n }} + + + +

+ {{ - "domainStatusUnverified" | i18n + ((accountDeprovisioningEnabled$ | async) + ? "domainStatusUnderVerification" + : "domainStatusUnverified" + ) | i18n }} {{ - "domainStatusVerified" | i18n + ((accountDeprovisioningEnabled$ | async) + ? "domainStatusClaimed" + : "domainStatusVerified" + ) | i18n }} @@ -70,7 +90,10 @@ type="button" > - {{ "verifyDomain" | i18n }} + {{ + ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") + | i18n + }} + + + + + {{ "loading" | i18n }} + + + +

{{ "noClientsInList" | i18n }}

+ + + + {{ "name" | i18n }} + {{ "numberOfUsers" | i18n }} + {{ "billingPlan" | i18n }} + + + + + + + + {{ row.organizationName }} + + + {{ row.userCount }} + / {{ row.seats }} + + + {{ row.plan }} + + + + + + + +
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts new file mode 100644 index 00000000000..2be38477d4c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts @@ -0,0 +1,167 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; +import { firstValueFrom, from, map } from "rxjs"; +import { debounceTime, first, switchMap } from "rxjs/operators"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { PlanType } from "@bitwarden/common/billing/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { + AvatarModule, + DialogService, + TableDataSource, + TableModule, + ToastService, +} from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; + +import { WebProviderService } from "../services/web-provider.service"; + +import { AddOrganizationComponent } from "./add-organization.component"; + +const DisallowedPlanTypes = [ + PlanType.Free, + PlanType.FamiliesAnnually2019, + PlanType.FamiliesAnnually, + PlanType.TeamsStarter2023, + PlanType.TeamsStarter, +]; + +@Component({ + templateUrl: "vnext-clients.component.html", + standalone: true, + imports: [ + SharedOrganizationModule, + HeaderModule, + CommonModule, + JslibModule, + AvatarModule, + RouterModule, + TableModule, + ], +}) +export class vNextClientsComponent { + providerId: string = ""; + addableOrganizations: Organization[] = []; + loading = true; + manageOrganizations = false; + showAddExisting = false; + dataSource: TableDataSource = + new TableDataSource(); + protected searchControl = new FormControl("", { nonNullable: true }); + + constructor( + private router: Router, + private providerService: ProviderService, + private apiService: ApiService, + private organizationService: OrganizationService, + private organizationApiService: OrganizationApiServiceAbstraction, + private activatedRoute: ActivatedRoute, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private validationService: ValidationService, + private webProviderService: WebProviderService, + ) { + this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); + + this.activatedRoute.parent?.params + ?.pipe( + switchMap((params) => { + this.providerId = params.providerId; + return this.providerService.get$(this.providerId).pipe( + map((provider) => provider?.providerStatus === ProviderStatusType.Billable), + map((isBillable) => { + if (isBillable) { + return from( + this.router.navigate(["../manage-client-organizations"], { + relativeTo: this.activatedRoute, + }), + ); + } else { + return from(this.load()); + } + }), + ); + }), + takeUntilDestroyed(), + ) + .subscribe(); + + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((searchText) => { + this.dataSource.filter = (data) => + data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + }); + } + + async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.organizationName, + content: { key: "detachOrganizationConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.webProviderService.detachOrganization(this.providerId, organization.id); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("detachedOrganization", organization.organizationName), + }); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + } + + async load() { + const response = await this.apiService.getProviderClients(this.providerId); + const clients = response.data != null && response.data.length > 0 ? response.data : []; + this.dataSource.data = clients; + this.manageOrganizations = + (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; + const candidateOrgs = (await this.organizationService.getAll()).filter( + (o) => o.isOwner && o.providerId == null, + ); + const allowedOrgsIds = await Promise.all( + candidateOrgs.map((o) => this.organizationApiService.get(o.id)), + ).then((orgs) => + orgs.filter((o) => !DisallowedPlanTypes.includes(o.planType)).map((o) => o.id), + ); + this.addableOrganizations = candidateOrgs.filter((o) => allowedOrgsIds.includes(o.id)); + + this.showAddExisting = this.addableOrganizations.length !== 0; + this.loading = false; + } + + async addExistingOrganization() { + const dialogRef = AddOrganizationComponent.open(this.dialogService, { + providerId: this.providerId, + organizations: this.addableOrganizations, + }); + + if (await firstValueFrom(dialogRef.closed)) { + await this.load(); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts index a92db7edd14..086be39b754 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TestBed } from "@angular/core/testing"; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.ts index dd4db2528b6..6146e990972 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/guards/provider-permissions.guard.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts index 3b38cc4763b..bd8c54c39d7 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts index 5f88bf177ca..c976ac5b62f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts index 644070c12d2..6ab07cc0794 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts index b5d5274498c..9f58ee4d29b 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts index 0517715d425..6390d13ee16 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts index 86d52c38159..16f794bd6d2 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -50,8 +52,8 @@ export class MembersComponent extends BaseMembersComponent { dataSource = new MembersTableDataSource(); loading = true; providerId: string; - rowHeight = 62; - rowHeightClass = `tw-h-[62px]`; + rowHeight = 69; + rowHeightClass = `tw-h-[69px]`; status: ProviderUserStatusType = null; userStatusType = ProviderUserStatusType; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts index fde45224ab4..82671a7e418 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html index 0536221cafd..a20dd1379e2 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html @@ -5,7 +5,7 @@ diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts index 820fc820ace..3f1a7ff3989 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts @@ -1,13 +1,15 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, RouterModule } from "@angular/router"; -import { switchMap, Observable, Subject, combineLatest, map } from "rxjs"; +import { combineLatest, map, Observable, Subject, switchMap } from "rxjs"; import { takeUntil } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { BannerModule, IconModule, LinkModule } from "@bitwarden/components"; @@ -36,7 +38,7 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); protected provider$: Observable; - protected hasConsolidatedBilling$: Observable; + protected isBillable: Observable; protected canAccessBilling$: Observable; protected showProviderClientVaultPrivacyWarningBanner$ = this.configService.getFeatureFlag$( @@ -58,12 +60,12 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { takeUntil(this.destroy$), ); - this.hasConsolidatedBilling$ = this.provider$.pipe( - hasConsolidatedBilling(this.configService), + this.isBillable = this.provider$.pipe( + map((provider) => provider?.providerStatus === ProviderStatusType.Billable), takeUntil(this.destroy$), ); - this.canAccessBilling$ = combineLatest([this.hasConsolidatedBilling$, this.provider$]).pipe( + this.canAccessBilling$ = combineLatest([this.isBillable, this.provider$]).pipe( map( ([hasConsolidatedBilling, provider]) => hasConsolidatedBilling && provider.isProviderAdmin, ), diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index 00c944e69bb..09276263332 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -2,8 +2,10 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; +import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component"; import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component"; @@ -12,10 +14,12 @@ import { ProviderSubscriptionComponent, hasConsolidatedBilling, ProviderBillingHistoryComponent, + vNextManageClientsComponent, } from "../../billing/providers"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; +import { vNextClientsComponent } from "./clients/vnext-clients.component"; import { providerPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { EventsComponent } from "./manage/events.component"; @@ -82,13 +86,25 @@ const routes: Routes = [ children: [ { path: "", pathMatch: "full", redirectTo: "clients" }, { path: "clients/create", component: CreateOrganizationComponent }, - { path: "clients", component: ClientsComponent, data: { titleId: "clients" } }, - { - path: "manage-client-organizations", - canActivate: [hasConsolidatedBilling], - component: ManageClientsComponent, - data: { titleId: "clients" }, - }, + ...featureFlaggedRoute({ + defaultComponent: ClientsComponent, + flaggedComponent: vNextClientsComponent, + featureFlag: FeatureFlag.PM12443RemovePagingLogic, + routeOptions: { + path: "clients", + data: { titleId: "clients" }, + }, + }), + ...featureFlaggedRoute({ + defaultComponent: ManageClientsComponent, + flaggedComponent: vNextManageClientsComponent, + featureFlag: FeatureFlag.PM12443RemovePagingLogic, + routeOptions: { + path: "manage-client-organizations", + data: { titleId: "clients" }, + canActivate: [hasConsolidatedBilling], + }, + }), { path: "manage", children: [ diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts index 2419f239766..ac2fb333fae 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 6f013d27f76..264b43aee9d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts index 0442f04fb72..e0a4eaedce1 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 482b85b7127..74aa468c42e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -24,14 +24,12 @@

{{ "generalInformation" | i18n }}

{{ "billingEmail" | i18n }} - {{ - "providerBillingEmailHint" | i18n - }} + {{ "providerBillingEmailHint" | i18n }}
- - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index aaad0ce4578..ecf649b8f31 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -1,14 +1,15 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, switchMap } from "rxjs"; +import { Subject, switchMap } from "rxjs"; import { first, takeUntil } from "rxjs/operators"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -22,8 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; templateUrl: "setup.component.html", }) export class SetupComponent implements OnInit, OnDestroy { - @ViewChild(ManageTaxInformationComponent) - manageTaxInformationComponent: ManageTaxInformationComponent; + @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; loading = true; providerId: string; @@ -34,10 +34,6 @@ export class SetupComponent implements OnInit, OnDestroy { billingEmail: ["", [Validators.required, Validators.email]], }); - protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$( - FeatureFlag.EnableConsolidatedBilling, - ); - private destroy$ = new Subject(); constructor( @@ -112,15 +108,9 @@ export class SetupComponent implements OnInit, OnDestroy { submit = async () => { try { - const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$); - this.formGroup.markAllAsTouched(); - const formIsValid = consolidatedBillingEnabled - ? this.formGroup.valid && this.manageTaxInformationComponent.touch() - : this.formGroup.valid; - - if (!formIsValid) { + if (!this.taxInformationComponent.validate() || !this.formGroup.valid) { return; } @@ -133,20 +123,16 @@ export class SetupComponent implements OnInit, OnDestroy { request.token = this.token; request.key = key; - if (consolidatedBillingEnabled) { - request.taxInfo = new ExpandedTaxInfoUpdateRequest(); - const taxInformation = this.manageTaxInformationComponent.getTaxInformation(); - - request.taxInfo.country = taxInformation.country; - request.taxInfo.postalCode = taxInformation.postalCode; - if (taxInformation.includeTaxId) { - request.taxInfo.taxId = taxInformation.taxId; - request.taxInfo.line1 = taxInformation.line1; - request.taxInfo.line2 = taxInformation.line2; - request.taxInfo.city = taxInformation.city; - request.taxInfo.state = taxInformation.state; - } - } + request.taxInfo = new ExpandedTaxInfoUpdateRequest(); + const taxInformation = this.taxInformationComponent.getTaxInformation(); + + request.taxInfo.country = taxInformation.country; + request.taxInfo.postalCode = taxInformation.postalCode; + request.taxInfo.taxId = taxInformation.taxId; + request.taxInfo.line1 = taxInformation.line1; + request.taxInfo.line2 = taxInformation.line2; + request.taxInfo.city = taxInformation.city; + request.taxInfo.state = taxInformation.state; const provider = await this.providerApiService.postProviderSetup(this.providerId, request); @@ -160,6 +146,7 @@ export class SetupComponent implements OnInit, OnDestroy { await this.router.navigate(["/providers", provider.id]); } catch (e) { + e.message = this.i18nService.translate(e.message) || e.message; this.validationService.showError(e); } }; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts index a4461b3e11a..83a87d8bc6c 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts @@ -1,4 +1,7 @@ -import { Component, OnInit } from "@angular/core"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, OnInit, SecurityContext } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; @@ -24,6 +27,7 @@ export class VerifyRecoverDeleteProviderComponent implements OnInit { private i18nService: I18nService, private route: ActivatedRoute, private toastService: ToastService, + private sanitizer: DomSanitizer, ) {} async ngOnInit() { @@ -31,7 +35,10 @@ export class VerifyRecoverDeleteProviderComponent implements OnInit { if (qParams.providerId != null && qParams.token != null && qParams.name != null) { this.providerId = qParams.providerId; this.token = qParams.token; - this.name = qParams.name; + this.name = + qParams.name && typeof qParams.name === "string" + ? this.sanitizer.sanitize(SecurityContext.HTML, qParams.name) || "" + : ""; } else { await this.router.navigate(["/"]); } diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index 9431e77e256..1e0f60e2cd2 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -7,6 +7,7 @@ import { ActivateAutofillPolicy } from "./admin-console/policies/activate-autofi import { AutomaticAppLoginPolicy } from "./admin-console/policies/automatic-app-login.component"; import { DisablePersonalVaultExportPolicy } from "./admin-console/policies/disable-personal-vault-export.component"; import { MaximumVaultTimeoutPolicy } from "./admin-console/policies/maximum-vault-timeout.component"; +import { FreeFamiliesSponsorshipPolicy } from "./billing/policies/free-families-sponsorship.component"; @Component({ selector: "app-root", @@ -19,9 +20,17 @@ export class AppComponent extends BaseAppComponent implements OnInit { this.policyListService.addPolicies([ new MaximumVaultTimeoutPolicy(), new DisablePersonalVaultExportPolicy(), - new ActivateAutofillPolicy(), ]); + this.configService + .getFeatureFlag(FeatureFlag.DisableFreeFamiliesSponsorship) + .then((isFreeFamilyEnabled) => { + if (isFreeFamilyEnabled) { + this.policyListService.addPolicies([new FreeFamiliesSponsorshipPolicy()]); + } + this.policyListService.addPolicies([new ActivateAutofillPolicy()]); + }); + this.configService.getFeatureFlag(FeatureFlag.IdpAutoSubmitLogin).then((enabled) => { if ( enabled && diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index 8f2074262d7..3a78ae0ed01 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -4,7 +4,7 @@ import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouterModule } from "@angular/router"; -import { InfiniteScrollModule } from "ngx-infinite-scroll"; +import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CoreModule } from "@bitwarden/web-vault/app/core"; @@ -19,6 +19,8 @@ import { DisablePersonalVaultExportPolicyComponent } from "./admin-console/polic import { MaximumVaultTimeoutPolicyComponent } from "./admin-console/policies/maximum-vault-timeout.component"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; +import { FreeFamiliesSponsorshipPolicyComponent } from "./billing/policies/free-families-sponsorship.component"; +import { AccessIntelligenceModule } from "./tools/access-intelligence/access-intelligence.module"; /** * This is the AppModule for the commercial version of Bitwarden. @@ -35,11 +37,12 @@ import { AppComponent } from "./app.component"; FormsModule, ReactiveFormsModule, CoreModule, - InfiniteScrollModule, + InfiniteScrollDirective, DragDropModule, AppRoutingModule, OssRoutingModule, OrganizationsModule, // Must be after OssRoutingModule for competing routes to resolve properly + AccessIntelligenceModule, RouterModule, WildcardRoutingModule, // Needs to be last to catch all non-existing routes ], @@ -49,6 +52,7 @@ import { AppComponent } from "./app.component"; MaximumVaultTimeoutPolicyComponent, ActivateAutofillPolicyComponent, AutomaticAppLoginPolicyComponent, + FreeFamiliesSponsorshipPolicyComponent, ], bootstrap: [AppComponent], }) diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html index 914d015110b..0731820e413 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html @@ -31,7 +31,10 @@ {{ "ssoIdentifierHintPartOne" | i18n }} - {{ "domainVerification" | i18n }} + {{ + ((accountDeprovisioningEnabled$ | async) ? "claimedDomains" : "domainVerification") + | i18n + }} @@ -126,7 +129,7 @@ />

{{ "spMetadataUrl" | i18n }} + > + + + + + + + + +
+ +
+ diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts new file mode 100644 index 00000000000..94f615f0cee --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts @@ -0,0 +1,201 @@ +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom, from, lastValueFrom, map } from "rxjs"; +import { debounceTime, first, switchMap } from "rxjs/operators"; + +import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; +import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { + AvatarModule, + DialogService, + TableDataSource, + TableModule, + ToastService, +} from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; + +import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; + +import { + CreateClientDialogResultType, + openCreateClientDialog, +} from "./create-client-dialog.component"; +import { + ManageClientNameDialogResultType, + openManageClientNameDialog, +} from "./manage-client-name-dialog.component"; +import { + ManageClientSubscriptionDialogResultType, + openManageClientSubscriptionDialog, +} from "./manage-client-subscription-dialog.component"; +import { ReplacePipe } from "./replace.pipe"; +import { vNextNoClientsComponent } from "./vnext-no-clients.component"; + +@Component({ + templateUrl: "vnext-manage-clients.component.html", + standalone: true, + imports: [ + AvatarModule, + TableModule, + HeaderModule, + SharedOrganizationModule, + vNextNoClientsComponent, + ReplacePipe, + ], +}) +export class vNextManageClientsComponent { + providerId: string = ""; + provider: Provider | undefined; + loading = true; + isProviderAdmin = false; + dataSource: TableDataSource = + new TableDataSource(); + + protected searchControl = new FormControl("", { nonNullable: true }); + protected plans: PlanResponse[] = []; + + constructor( + private billingApiService: BillingApiServiceAbstraction, + private providerService: ProviderService, + private router: Router, + private activatedRoute: ActivatedRoute, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private validationService: ValidationService, + private webProviderService: WebProviderService, + ) { + this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); + + this.activatedRoute.parent?.params + ?.pipe( + switchMap((params) => { + this.providerId = params.providerId; + return this.providerService.get$(this.providerId).pipe( + map((provider: Provider) => provider?.providerStatus === ProviderStatusType.Billable), + map((isBillable) => { + if (!isBillable) { + return from( + this.router.navigate(["../clients"], { + relativeTo: this.activatedRoute, + }), + ); + } else { + return from(this.load()); + } + }), + ); + }), + takeUntilDestroyed(), + ) + .subscribe(); + + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((searchText) => { + this.dataSource.filter = (data) => + data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + }); + } + + async load() { + this.provider = await firstValueFrom(this.providerService.get$(this.providerId)); + + this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin; + + const clients = (await this.billingApiService.getProviderClientOrganizations(this.providerId)) + .data; + + this.dataSource.data = clients; + + this.plans = (await this.billingApiService.getPlans()).data; + + this.loading = false; + } + + createClient = async () => { + const reference = openCreateClientDialog(this.dialogService, { + data: { + providerId: this.providerId, + plans: this.plans, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === CreateClientDialogResultType.Submitted) { + await this.load(); + } + }; + + manageClientName = async (organization: ProviderOrganizationOrganizationDetailsResponse) => { + const dialogRef = openManageClientNameDialog(this.dialogService, { + data: { + providerId: this.providerId, + organization: { + id: organization.id, + name: organization.organizationName, + seats: organization.seats ? organization.seats : 0, + }, + }, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result === ManageClientNameDialogResultType.Submitted) { + await this.load(); + } + }; + + manageClientSubscription = async ( + organization: ProviderOrganizationOrganizationDetailsResponse, + ) => { + const dialogRef = openManageClientSubscriptionDialog(this.dialogService, { + data: { + organization, + provider: this.provider!, + }, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result === ManageClientSubscriptionDialogResultType.Submitted) { + await this.load(); + } + }; + + async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.organizationName, + content: { key: "detachOrganizationConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.webProviderService.detachOrganization(this.providerId, organization.id); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("detachedOrganization", organization.organizationName), + }); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts new file mode 100644 index 00000000000..5ad19945c51 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts @@ -0,0 +1,50 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; + +import { svgIcon } from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; + +const gearIcon = svgIcon` + + + + + + + + + + + + + + + + +`; + +@Component({ + selector: "app-no-clients", + standalone: true, + imports: [SharedOrganizationModule], + template: `
+ +

{{ "noClients" | i18n }}

+ + + {{ "addNewOrganization" | i18n }} + +
`, +}) +export class vNextNoClientsComponent { + icon = gearIcon; + @Input() showAddOrganizationButton = true; + @Output() addNewOrganizationClicked = new EventEmitter(); + + addNewOrganization = () => this.addNewOrganizationClicked.emit(); +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts b/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts index 213b9a53681..60dbf4b3b82 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts @@ -4,24 +4,13 @@ import { firstValueFrom } from "rxjs"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType } 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"; export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => { - const configService = inject(ConfigService); const providerService = inject(ProviderService); const provider = await firstValueFrom(providerService.get$(route.params.providerId)); - const consolidatedBillingEnabled = await configService.getFeatureFlag( - FeatureFlag.EnableConsolidatedBilling, - ); - - if ( - !consolidatedBillingEnabled || - !provider || - provider.providerStatus !== ProviderStatusType.Billable - ) { + if (!provider || provider.providerStatus !== ProviderStatusType.Billable) { return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]); } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts index dea7d4ca197..6cf0b21169b 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component, Input } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 1ea888d6ebc..a6b27b9f3dd 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, concatMap, takeUntil } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts deleted file mode 100644 index 058d59d702c..00000000000 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, Input } from "@angular/core"; - -import { IntegrationType } from "@bitwarden/common/enums"; - -import { Integration } from "../models/integration"; - -@Component({ - selector: "sm-integration-grid", - templateUrl: "./integration-grid.component.html", -}) -export class IntegrationGridComponent { - @Input() integrations: Integration[]; - - protected IntegrationType = IntegrationType; -} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html index a2f21888613..db3db75897d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html @@ -4,7 +4,11 @@

{{ "integrationsDesc" | i18n }}

- +
@@ -12,5 +16,9 @@

{{ "sdks" | i18n }}

{{ "sdksDesc" | i18n }}

- +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index 6c8ea28bc2f..2d0a681460a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -4,14 +4,16 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; -import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../libs/angular/src/services/injection-tokens"; -import { I18nService } from "../../../../../../libs/common/src/platform/abstractions/i18n.service"; -import { ThemeType } from "../../../../../../libs/common/src/platform/enums"; -import { ThemeStateService } from "../../../../../../libs/common/src/platform/theming/theme-state.service"; -import { I18nPipe } from "../../../../../../libs/components/src/shared/i18n.pipe"; +import {} from "@bitwarden/web-vault/app/shared"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { IntegrationCardComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component"; +import { IntegrationGridComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component"; -import { IntegrationCardComponent } from "./integration-card/integration-card.component"; -import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; import { IntegrationsComponent } from "./integrations.component"; @Component({ @@ -31,18 +33,12 @@ describe("IntegrationsComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - IntegrationsComponent, - IntegrationGridComponent, - IntegrationCardComponent, - MockHeaderComponent, - MockNewMenuComponent, - I18nPipe, - ], + declarations: [IntegrationsComponent, MockHeaderComponent, MockNewMenuComponent], + imports: [JslibModule, IntegrationGridComponent, IntegrationCardComponent], providers: [ { provide: I18nService, - useValue: mock({ t: (key) => key }), + useValue: mock(), }, { provide: ThemeStateService, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts index 9e846d45034..cdae129de4f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -1,9 +1,7 @@ import { Component } from "@angular/core"; import { IntegrationType } from "@bitwarden/common/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -import { Integration } from "./models/integration"; +import { Integration } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/models"; @Component({ selector: "sm-integrations", @@ -12,19 +10,17 @@ import { Integration } from "./models/integration"; export class IntegrationsComponent { private integrationsAndSdks: Integration[] = []; - constructor(i18nService: I18nService) { + constructor() { this.integrationsAndSdks = [ { name: "Rust", - linkText: i18nService.t("rustSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk", + linkURL: "https://github.com/bitwarden/sdk-sm", image: "../../../../../../../images/secrets-manager/sdks/rust.svg", imageDarkMode: "../../../../../../../images/secrets-manager/sdks/rust-white.svg", type: IntegrationType.SDK, }, { name: "GitHub Actions", - linkText: i18nService.t("setUpGithubActions"), linkURL: "https://bitwarden.com/help/github-actions-integration/", image: "../../../../../../../images/secrets-manager/integrations/github.svg", imageDarkMode: "../../../../../../../images/secrets-manager/integrations/github-white.svg", @@ -32,7 +28,6 @@ export class IntegrationsComponent { }, { name: "GitLab CI/CD", - linkText: i18nService.t("setUpGitlabCICD"), linkURL: "https://bitwarden.com/help/gitlab-integration/", image: "../../../../../../../images/secrets-manager/integrations/gitlab.svg", imageDarkMode: "../../../../../../../images/secrets-manager/integrations/gitlab-white.svg", @@ -40,71 +35,61 @@ export class IntegrationsComponent { }, { name: "Ansible", - linkText: i18nService.t("setUpAnsible"), linkURL: "https://bitwarden.com/help/ansible-integration/", image: "../../../../../../../images/secrets-manager/integrations/ansible.svg", type: IntegrationType.Integration, }, { name: "C#", - linkText: i18nService.t("cSharpSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/csharp", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/csharp", image: "../../../../../../../images/secrets-manager/sdks/c-sharp.svg", type: IntegrationType.SDK, }, { name: "C++", - linkText: i18nService.t("cPlusPlusSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/cpp", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/cpp", image: "../../../../../../../images/secrets-manager/sdks/c-plus-plus.png", type: IntegrationType.SDK, }, { name: "Go", - linkText: i18nService.t("goSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/go", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/go", image: "../../../../../../../images/secrets-manager/sdks/go.svg", type: IntegrationType.SDK, }, { name: "Java", - linkText: i18nService.t("javaSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/java", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/java", image: "../../../../../../../images/secrets-manager/sdks/java.svg", imageDarkMode: "../../../../../../../images/secrets-manager/sdks/java-white.svg", type: IntegrationType.SDK, }, { name: "JS WebAssembly", - linkText: i18nService.t("jsWebAssemblySDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/js", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/js", image: "../../../../../../../images/secrets-manager/sdks/wasm.svg", type: IntegrationType.SDK, }, { name: "php", - linkText: i18nService.t("phpSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/php", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/php", image: "../../../../../../../images/secrets-manager/sdks/php.svg", type: IntegrationType.SDK, }, { name: "Python", - linkText: i18nService.t("pythonSDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/python", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/python", image: "../../../../../../../images/secrets-manager/sdks/python.svg", type: IntegrationType.SDK, }, { name: "Ruby", - linkText: i18nService.t("rubySDKRepo"), - linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/ruby", + linkURL: "https://github.com/bitwarden/sdk-sm/tree/main/languages/ruby", image: "../../../../../../../images/secrets-manager/sdks/ruby.png", type: IntegrationType.SDK, }, { name: "Kubernetes Operator", - linkText: i18nService.t("setUpKubernetes"), linkURL: "https://bitwarden.com/help/secrets-manager-kubernetes-operator/", image: "../../../../../../../images/secrets-manager/integrations/kubernetes.svg", type: IntegrationType.Integration, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts index 0d26b626f16..eee426e3b07 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts @@ -1,15 +1,20 @@ import { NgModule } from "@angular/core"; +import { IntegrationCardComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component"; +import { IntegrationGridComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component"; + import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; -import { IntegrationCardComponent } from "./integration-card/integration-card.component"; -import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; import { IntegrationsRoutingModule } from "./integrations-routing.module"; import { IntegrationsComponent } from "./integrations.component"; @NgModule({ - imports: [SecretsManagerSharedModule, IntegrationsRoutingModule], - declarations: [IntegrationsComponent, IntegrationGridComponent, IntegrationCardComponent], - providers: [], + imports: [ + SecretsManagerSharedModule, + IntegrationsRoutingModule, + IntegrationCardComponent, + IntegrationGridComponent, + ], + declarations: [IntegrationsComponent], }) export class IntegrationsModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts index 32ff1c944fc..adf01afd10c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/access-policy.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/access-policy.view.ts index 0863bb0cccf..676f1447f3e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/access-policy.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/access-policy.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore class BaseAccessPolicyView { read: boolean; write: boolean; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/potential-grantee.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/potential-grantee.view.ts index 5a6af6eb13c..75649f8a43b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/potential-grantee.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/potential-grantee.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class PotentialGranteeView { id: string; name: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-people-access-policies.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-people-access-policies.view.ts index 9a35e76a615..2cf3986d10d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-people-access-policies.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-people-access-policies.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { GroupAccessPolicyView, UserAccessPolicyView } from "./access-policy.view"; export class ProjectPeopleAccessPoliciesView { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-service-accounts-access-policies.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-service-accounts-access-policies.view.ts index 9faaa29b697..94ecdab5318 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-service-accounts-access-policies.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/project-service-accounts-access-policies.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ServiceAccountAccessPolicyView } from "./access-policy.view"; export class ProjectServiceAccountsAccessPoliciesView { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/secret-access-policies.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/secret-access-policies.view.ts index 8742021a421..41cf4e637a3 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/secret-access-policies.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/secret-access-policies.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { GroupAccessPolicyView, UserAccessPolicyView, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-granted-policies.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-granted-policies.view.ts index e055daa199a..9a007b823ca 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-granted-policies.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-granted-policies.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { GrantedProjectAccessPolicyView } from "./access-policy.view"; export class ServiceAccountGrantedPoliciesView { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-people-access-policies.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-people-access-policies.view.ts index 2ef4eedc33e..2fd7e54994d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-people-access-policies.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/service-account-people-access-policies.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { GroupAccessPolicyView, UserAccessPolicyView } from "./access-policy.view"; export class ServiceAccountPeopleAccessPoliciesView { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts index 5f0aa9647cf..df15d3fc373 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ProjectListView { id: string; organizationId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts index 09091d2a754..7f501467d32 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ProjectView { id: string; organizationId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts index a8c4ea51c38..7d28236d6f7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-list.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretProjectView } from "./secret-project.view"; export class SecretListView { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts index 7feb2804b9c..50951b0cd90 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret-project.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SecretProjectView { id: string; name: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts index f08a639a590..5d221ba03df 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/secret.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretProjectView } from "./secret-project.view"; export class SecretView { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts index a0ce182a02d..3df383ef96c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/service-account.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ServiceAccountView { id: string; organizationId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html index 31746e7601c..2de9a4c4f30 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html @@ -10,12 +10,12 @@ {{ freeTrial.message }} - {{ "routeToPaymentMethodTrigger" | i18n }} + {{ "clickHereToAddPaymentMethod" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index bf2dbb76ad3..a95192e0d91 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { @@ -20,6 +22,7 @@ import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -114,9 +117,9 @@ export class OverviewComponent implements OnInit, OnDestroy { private smOnboardingTasksService: SMOnboardingTasksService, private logService: LogService, private router: Router, - private organizationApiService: OrganizationApiServiceAbstraction, private trialFlowService: TrialFlowService, + private organizationBillingService: OrganizationBillingServiceAbstraction, ) {} ngOnInit() { @@ -139,20 +142,16 @@ export class OverviewComponent implements OnInit, OnDestroy { }); this.freeTrial$ = org$.pipe( - filter((org) => org.isOwner), + filter((org) => org.isOwner && org.canViewBillingHistory && org.canViewSubscription), switchMap((org) => combineLatest([ of(org), this.organizationApiService.getSubscription(org.id), - this.organizationApiService.getBilling(org.id), + this.organizationBillingService.getPaymentSource(org.id), ]), ), - map(([org, sub, billing]) => { - return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( - org, - sub, - billing?.paymentSource, - ); + map(([org, sub, paymentSource]) => { + return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource); }), takeUntil(this.destroy$), ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts index b9c09a0d671..94d9ab2d8ee 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -1,8 +1,8 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +import { OnboardingModule } from "@bitwarden/web-vault/app/shared/components/onboarding/onboarding.module"; -import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { OverviewRoutingModule } from "./overview-routing.module"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts index feb366823cb..3380d280d44 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index 09f3d9573ac..f266ff0adf0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 631f6409748..78d367776d4 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -8,8 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -import { RouterService } from "../../../../../../../apps/web/src/app/core/router.service"; import { ProjectView } from "../../models/view/project.view"; import { ProjectService } from "../project.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts index 1f694d6999a..2c6723a56a2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts index 8e9f2c72c10..18d3dad6ea6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/models/requests/project.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; export class ProjectRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts index db1ee181e6c..ee2395b3f83 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts index 2500416880b..4a0c37cb4a9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index ce42fcada60..b9c6d86cefc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, combineLatestWith, filter, Observable, startWith, switchMap } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts index 4ac14b3b362..d289f9f7b1f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index fb7f2d74528..08c9446e485 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts index fd59014642a..8ced29fa08e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, lastValueFrom, Observable, startWith, switchMap } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts index 10d58bc2ea5..40d359e9246 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index b656570a54c..c12930bb048 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts index 9723bc9ed6c..e8658112a50 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/requests/secret.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretAccessPoliciesRequest } from "../../shared/access-policies/models/requests/secret-access-policies.request"; export class SecretRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts index 4547831ae1d..950d3c42ccb 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secret.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index 1744e970361..5d1ed780816 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts index e24b0488b2c..caca8c92aa8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, Output } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts index 25ad93706ab..a4f0c077efd 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts index 6b04e53feba..8eb4a5120a2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts index ecada8be72a..5aca91166a6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts index fbc00587897..4f761f7f279 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts index e224b8e1e9a..2273b42897c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component, Input, OnDestroy, OnInit } from "@angular/core"; import { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts index 47deeb2a418..96e3b58b633 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params } from "@angular/router"; import { Subject, concatMap, takeUntil } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts index c44daced812..58587065265 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 27e9152b474..bf283ad5a5b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts index f231a273fc5..6538ae49adb 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index ebd00a83ea5..a4079a6def3 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -8,8 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -import { RouterService } from "../../../../../../../../clients/apps/web/src/app/core/router.service"; import { ServiceAccountView } from "../../models/view/service-account.view"; import { ServiceAccountService } from "../service-account.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts index 0aa83aa0e96..0f182a5ca8c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts index 3801e7398fe..37232cc86ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/access-token.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; export class AccessTokenRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/revoke-access-tokens.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/revoke-access-tokens.request.ts index 51266e802a0..4ef87e3090e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/revoke-access-tokens.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/revoke-access-tokens.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class RevokeAccessTokensRequest { ids: string[]; } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts index 47221b2b82a..6d7d688fee0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/requests/service-account.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; export class ServiceAccountRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts index e9678a0f3fc..2ecece44956 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/models/view/access-token.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class AccessTokenView { id: string; name: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts index 58675b15b37..a57251c4f77 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts index 2ee95848012..34a48af3055 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index de2544ffd26..74465e8ecef 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, combineLatest, filter, startWith, switchMap, takeUntil } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts index dcca8719483..a85f5c5e09a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts index 75edd0f0932..cbffc80aa05 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; import { Subject, takeUntil } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index d015cccd99d..b134cb94cde 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, Observable, startWith, switchMap } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error-line.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error-line.ts index 79efeaec09d..e204934b2aa 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error-line.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error-line.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SecretsManagerImportErrorLine { id: number; type: "Project" | "Secret"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error.ts index e362a8f2593..da008ce277a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/error/sm-import-error.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretsManagerImportErrorLine } from "./sm-import-error-line"; export class SecretsManagerImportError extends Error { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-import.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-import.request.ts index 4ef2a7181eb..7f5ba46777c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-import.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-import.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretsManagerImportedProjectRequest } from "./sm-imported-project.request"; import { SecretsManagerImportedSecretRequest } from "./sm-imported-secret.request"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-project.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-project.request.ts index bee91972bb8..12e8f8452dc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-project.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-project.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; export class SecretsManagerImportedProjectRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-secret.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-secret.request.ts index 55c5382b3ad..f9629e7c317 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-secret.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/requests/sm-imported-secret.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; export class SecretsManagerImportedSecretRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/sm-export.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/sm-export.ts index 96203210789..0025ab592bd 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/sm-export.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/models/sm-export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SecretsManagerExport { projects: SecretsManagerExportProject[]; secrets: SecretsManagerExportSecret[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index 713a93b643d..d607eabbe0f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts index 3e46ea7f316..262f1a6b161 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts index 4051be51a7a..c5934067fd7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; @@ -105,6 +107,8 @@ export class SecretsManagerPortingApiService { return secret; }), ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting.service.ts index 8f29e38a9e5..825a5ccc7ec 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { formatDate } from "@angular/common"; import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts index bf0091b619c..34de4e4860b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, forwardRef, Input, OnDestroy, OnInit } from "@angular/core"; import { ControlValueAccessor, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.type.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.type.ts index 52a91c5fd6d..607336e4747 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.type.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.type.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SelectItemView } from "@bitwarden/components"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts index c5de1e02787..4ae80d4decc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts index 9d0752b9878..920e12ef0cf 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/access-policy.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/access-policy.request.ts index 527ab3a6028..ab8e096d5d5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/access-policy.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/access-policy.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class AccessPolicyRequest { granteeId: string; read: boolean; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts index ddfca2bfb2b..888863c7efe 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class GrantedPolicyRequest { grantedId: string; read: boolean; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/secret-access-policies.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/secret-access-policies.request.ts index ac49dd49744..c3ede30ed5b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/secret-access-policies.request.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/secret-access-policies.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AccessPolicyRequest } from "./access-policy.request"; export class SecretAccessPoliciesRequest { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts index 8472ddb7b89..e2a851a6b82 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts index ce499ee9bda..8b046b7ff95 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil, concatMap } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 2d01908a365..0ca5ba09074 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { map } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts index 81c96c6b961..37b9524238f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; import { Subject, takeUntil } from "rxjs"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts index 9e9d41a29b9..d5c527abbb7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts index eae6c7c9b9e..75b935b851a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts index 917cea025c6..3a21dbe3b68 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; diff --git a/apps/web/src/app/tools/risk-insights/risk-insights-routing.module.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts similarity index 56% rename from apps/web/src/app/tools/risk-insights/risk-insights-routing.module.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts index 19cc6f6832c..993d9c0a134 100644 --- a/apps/web/src/app/tools/risk-insights/risk-insights-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts @@ -1,16 +1,15 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { RiskInsightsComponent } from "./risk-insights.component"; const routes: Routes = [ { - path: "", + path: "risk-insights", + canActivate: [organizationPermissionsGuard((org) => org.useRiskInsights)], component: RiskInsightsComponent, - canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)], data: { titleId: "RiskInsights", }, @@ -21,4 +20,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class RiskInsightsRoutingModule {} +export class AccessIntelligenceRoutingModule {} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts new file mode 100644 index 00000000000..2db7af4bb46 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from "@angular/core"; + +import { + MemberCipherDetailsApiService, + RiskInsightsDataService, + RiskInsightsReportService, +} from "@bitwarden/bit-common/tools/reports/risk-insights/services"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength/password-strength.service.abstraction"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; + +import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module"; +import { RiskInsightsComponent } from "./risk-insights.component"; + +@NgModule({ + imports: [RiskInsightsComponent, AccessIntelligenceRoutingModule], + providers: [ + { + provide: MemberCipherDetailsApiService, + deps: [ApiService], + }, + { + provide: RiskInsightsReportService, + deps: [ + PasswordStrengthServiceAbstraction, + AuditService, + CipherService, + MemberCipherDetailsApiService, + ], + }, + { + provide: RiskInsightsDataService, + deps: [RiskInsightsReportService], + }, + ], +}) +export class AccessIntelligenceModule {} diff --git a/apps/web/src/app/tools/risk-insights/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html similarity index 58% rename from apps/web/src/app/tools/risk-insights/all-applications.component.html rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index 4ed31adea78..e17ac078687 100644 --- a/apps/web/src/app/tools/risk-insights/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -1,16 +1,11 @@ -
- - {{ "loading" | i18n }} +
+
-
+

- {{ "noAppsInOrgTitle" | i18n: organization.name }} + {{ "noAppsInOrgTitle" | i18n: organization?.name }}

@@ -28,21 +23,23 @@

-
+

{{ "allApplications" | i18n }}

@@ -57,7 +54,7 @@

{{ "allApplications" | i18n }}

type="button" buttonType="secondary" bitButton - *ngIf="isCritialAppsFeatureEnabled" + *ngIf="isCriticalAppsFeatureEnabled" [disabled]="!selectedIds.size" [loading]="markingAsCritical" (click)="markAppsAsCritical()" @@ -69,17 +66,17 @@

{{ "allApplications" | i18n }}

- - {{ "application" | i18n }} - {{ "atRiskPasswords" | i18n }} - {{ "totalPasswords" | i18n }} - {{ "atRiskMembers" | i18n }} - {{ "totalMembers" | i18n }} + + {{ "application" | i18n }} + {{ "atRiskPasswords" | i18n }} + {{ "totalPasswords" | i18n }} + {{ "atRiskMembers" | i18n }} + {{ "totalMembers" | i18n }} - + {{ "allApplications" | i18n }}

(change)="onCheckboxChange(r.id, $event)" /> - - {{ r.name }} + + {{ r.applicationName }} - {{ r.atRiskPasswords }} + {{ r.atRiskPasswordCount }} - {{ r.totalPasswords }} + {{ r.passwordCount }} - {{ r.atRiskMembers }} + {{ r.atRiskMemberCount }} - {{ r.totalMembers }} + {{ r.memberCount }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts new file mode 100644 index 00000000000..5fb12fed090 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -0,0 +1,172 @@ +import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { debounceTime, map, Observable, of, Subscription } from "rxjs"; + +import { + RiskInsightsDataService, + RiskInsightsReportService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + ApplicationHealthReportDetail, + ApplicationHealthReportSummary, +} from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { + DialogService, + Icons, + NoItemsModule, + SearchModule, + TableDataSource, + ToastService, +} from "@bitwarden/components"; +import { CardComponent } from "@bitwarden/tools-card"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; + +import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; +import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component"; +import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; +import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; + +@Component({ + standalone: true, + selector: "tools-all-applications", + templateUrl: "./all-applications.component.html", + imports: [ + ApplicationsLoadingComponent, + HeaderModule, + CardComponent, + SearchModule, + PipesModule, + NoItemsModule, + SharedModule, + ], +}) +export class AllApplicationsComponent implements OnInit, OnDestroy { + protected dataSource = new TableDataSource(); + protected selectedIds: Set = new Set(); + protected searchControl = new FormControl("", { nonNullable: true }); + protected loading = true; + protected organization = {} as Organization; + noItemsIcon = Icons.Security; + protected markingAsCritical = false; + protected applicationSummary = {} as ApplicationHealthReportSummary; + private subscription = new Subscription(); + + destroyRef = inject(DestroyRef); + isLoading$: Observable = of(false); + isCriticalAppsFeatureEnabled = false; + + async ngOnInit() { + this.isCriticalAppsFeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.CriticalApps, + ); + + const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); + + if (organizationId) { + this.organization = await this.organizationService.get(organizationId); + this.subscription = this.dataService.applications$ + .pipe( + map((applications) => { + if (applications) { + this.dataSource.data = applications; + this.applicationSummary = + this.reportService.generateApplicationsSummary(applications); + } + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(); + this.isLoading$ = this.dataService.isLoading$; + } + } + + ngOnDestroy(): void { + this.subscription?.unsubscribe(); + } + + constructor( + protected cipherService: CipherService, + protected i18nService: I18nService, + protected activatedRoute: ActivatedRoute, + protected toastService: ToastService, + protected configService: ConfigService, + protected dataService: RiskInsightsDataService, + protected organizationService: OrganizationService, + protected reportService: RiskInsightsReportService, + protected dialogService: DialogService, + ) { + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((v) => (this.dataSource.filter = v)); + } + + goToCreateNewLoginItem = async () => { + // TODO: implement + this.toastService.showToast({ + variant: "warning", + title: "", + message: "Not yet implemented", + }); + }; + + markAppsAsCritical = async () => { + // TODO: Send to API once implemented + this.markingAsCritical = true; + return new Promise((resolve) => { + setTimeout(() => { + this.selectedIds.clear(); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("appsMarkedAsCritical"), + }); + resolve(true); + this.markingAsCritical = false; + }, 1000); + }); + }; + + trackByFunction(_: number, item: ApplicationHealthReportDetail) { + return item.applicationName; + } + + showAppAtRiskMembers = async (applicationName: string) => { + openAppAtRiskMembersDialog(this.dialogService, { + members: + this.dataSource.data.find((app) => app.applicationName === applicationName) + ?.atRiskMemberDetails ?? [], + applicationName, + }); + }; + + showOrgAtRiskMembers = async () => { + this.dialogService.open(OrgAtRiskMembersDialogComponent, { + data: this.reportService.generateAtRiskMemberList(this.dataSource.data), + }); + }; + + showOrgAtRiskApps = async () => { + this.dialogService.open(OrgAtRiskAppsDialogComponent, { + data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), + }); + }; + + onCheckboxChange(id: number, event: Event) { + const isChecked = (event.target as HTMLInputElement).checked; + if (isChecked) { + this.selectedIds.add(id); + } else { + this.selectedIds.delete(id); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html new file mode 100644 index 00000000000..383a1eccabe --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html @@ -0,0 +1,21 @@ + + {{ applicationName }} + +
+ {{ "atRiskMembersWithCount" | i18n: members.length }} + {{ + "atRiskMembersDescriptionWithApp" | i18n: applicationName + }} +
+ +
{{ member.email }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts new file mode 100644 index 00000000000..d6a757fe897 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts @@ -0,0 +1,35 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { MemberDetailsFlat } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; + +type AppAtRiskMembersDialogParams = { + members: MemberDetailsFlat[]; + applicationName: string; +}; + +export const openAppAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AppAtRiskMembersDialogParams, +) => + dialogService.open(AppAtRiskMembersDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./app-at-risk-members-dialog.component.html", + imports: [ButtonModule, CommonModule, JslibModule, DialogModule], +}) +export class AppAtRiskMembersDialogComponent { + protected members: MemberDetailsFlat[]; + protected applicationName: string; + + constructor(@Inject(DIALOG_DATA) private params: AppAtRiskMembersDialogParams) { + this.members = params.members; + this.applicationName = params.applicationName; + } +} diff --git a/apps/web/src/app/tools/risk-insights/application-table.mock.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts similarity index 100% rename from apps/web/src/app/tools/risk-insights/application-table.mock.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts diff --git a/apps/web/src/app/tools/risk-insights/critical-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html similarity index 100% rename from apps/web/src/app/tools/risk-insights/critical-applications.component.html rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html diff --git a/apps/web/src/app/tools/risk-insights/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts similarity index 87% rename from apps/web/src/app/tools/risk-insights/critical-applications.component.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts index 0779b2977e5..99f68aa9c72 100644 --- a/apps/web/src/app/tools/risk-insights/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; @@ -7,10 +9,9 @@ import { debounceTime, map } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SearchModule, TableDataSource, NoItemsModule, Icons } from "@bitwarden/components"; import { CardComponent } from "@bitwarden/tools-card"; - -import { HeaderModule } from "../../layouts/header/header.module"; -import { SharedModule } from "../../shared"; -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; import { applicationTableMockData } from "./application-table.mock"; import { RiskInsightsTabType } from "./risk-insights.component"; diff --git a/apps/web/src/app/tools/risk-insights/notified-members-table.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/notified-members-table.component.html similarity index 100% rename from apps/web/src/app/tools/risk-insights/notified-members-table.component.html rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/notified-members-table.component.html diff --git a/apps/web/src/app/tools/risk-insights/notified-members-table.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/notified-members-table.component.ts similarity index 100% rename from apps/web/src/app/tools/risk-insights/notified-members-table.component.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/notified-members-table.component.ts diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html new file mode 100644 index 00000000000..298011b2157 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html @@ -0,0 +1,25 @@ + + + {{ "atRiskApplicationsWithCount" | i18n: atRiskApps.length }} + + +
+ {{ "atRiskApplicationsDescription" | i18n }} +
+
{{ "application" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts new file mode 100644 index 00000000000..0ae00f60874 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskApplicationDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskApplicationDetail[], +) => + dialogService.open(OrgAtRiskAppsDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-apps-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskAppsDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskApps: AtRiskApplicationDetail[]) {} +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html new file mode 100644 index 00000000000..41ac8af7886 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html @@ -0,0 +1,27 @@ + + + {{ "atRiskMembersWithCount" | i18n: atRiskMembers.length }} + + +
+ {{ + "atRiskMembersDescription" | i18n + }} +
+
{{ "email" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts new file mode 100644 index 00000000000..72518843d94 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskMemberDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskMemberDetail[], +) => + dialogService.open(OrgAtRiskMembersDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-members-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskMembersDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskMembers: AtRiskMemberDetail[]) {} +} diff --git a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.html similarity index 100% rename from apps/web/src/app/tools/risk-insights/password-health-members-uri.component.html rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.html diff --git a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts similarity index 80% rename from apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts index b34730bd328..1e9e4171bc3 100644 --- a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts @@ -3,17 +3,19 @@ import { ActivatedRoute, convertToParamMap } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; -// eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TableModule } from "@bitwarden/components"; - -import { LooseComponentsModule } from "../../shared"; -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component"; @@ -46,6 +48,14 @@ describe("PasswordHealthMembersUriComponent", () => { url: of([]), }, }, + { + provide: MemberCipherDetailsApiService, + useValue: mock(), + }, + { + provide: ApiService, + useValue: mock(), + }, ], }).compileComponents(); }); diff --git a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.ts similarity index 83% rename from apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.ts index c977c829537..89597685799 100644 --- a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -5,8 +7,10 @@ import { ActivatedRoute } from "@angular/router"; import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -// eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -21,13 +25,8 @@ import { TableDataSource, TableModule, } from "@bitwarden/components"; - -// eslint-disable-next-line no-restricted-imports -import { HeaderModule } from "../../layouts/header/header.module"; -// eslint-disable-next-line no-restricted-imports -import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; -// eslint-disable-next-line no-restricted-imports -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @Component({ standalone: true, @@ -35,7 +34,6 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; templateUrl: "password-health-members-uri.component.html", imports: [ BadgeModule, - OrganizationBadgeModule, CommonModule, ContainerComponent, PipesModule, @@ -43,7 +41,7 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; HeaderModule, TableModule, ], - providers: [PasswordHealthService], + providers: [PasswordHealthService, MemberCipherDetailsApiService], }) export class PasswordHealthMembersURIComponent implements OnInit { passwordStrengthMap = new Map(); @@ -74,6 +72,7 @@ export class PasswordHealthMembersURIComponent implements OnInit { protected auditService: AuditService, protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, + protected memberCipherDetailsApiService: MemberCipherDetailsApiService, ) {} ngOnInit() { @@ -93,6 +92,7 @@ export class PasswordHealthMembersURIComponent implements OnInit { this.passwordStrengthService, this.auditService, this.cipherService, + this.memberCipherDetailsApiService, organizationId, ); diff --git a/apps/web/src/app/tools/risk-insights/password-health-members.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.html similarity index 100% rename from apps/web/src/app/tools/risk-insights/password-health-members.component.html rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.html diff --git a/apps/web/src/app/tools/risk-insights/password-health-members.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.ts similarity index 81% rename from apps/web/src/app/tools/risk-insights/password-health-members.component.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.ts index 2581de78ed5..0fff93de8d6 100644 --- a/apps/web/src/app/tools/risk-insights/password-health-members.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.ts @@ -1,11 +1,15 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormsModule } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { debounceTime, map } from "rxjs"; -// eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -18,30 +22,16 @@ import { TableModule, ToastService, } from "@bitwarden/components"; -import { CardComponent } from "@bitwarden/tools-card"; - -import { HeaderModule } from "../../layouts/header/header.module"; -// eslint-disable-next-line no-restricted-imports -import { SharedModule } from "../../shared"; -import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; -// eslint-disable-next-line no-restricted-imports -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @Component({ standalone: true, selector: "tools-password-health-members", templateUrl: "password-health-members.component.html", - imports: [ - CardComponent, - OrganizationBadgeModule, - PipesModule, - HeaderModule, - SearchModule, - FormsModule, - SharedModule, - TableModule, - ], - providers: [PasswordHealthService], + imports: [PipesModule, HeaderModule, SearchModule, FormsModule, SharedModule, TableModule], + providers: [PasswordHealthService, MemberCipherDetailsApiService], }) export class PasswordHealthMembersComponent implements OnInit { passwordStrengthMap = new Map(); @@ -69,6 +59,7 @@ export class PasswordHealthMembersComponent implements OnInit { protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, protected toastService: ToastService, + protected memberCipherDetailsApiService: MemberCipherDetailsApiService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -92,6 +83,7 @@ export class PasswordHealthMembersComponent implements OnInit { this.passwordStrengthService, this.auditService, this.cipherService, + this.memberCipherDetailsApiService, organizationId, ); diff --git a/apps/web/src/app/tools/risk-insights/password-health.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html similarity index 83% rename from apps/web/src/app/tools/risk-insights/password-health.component.html rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html index 5b1fe4610d9..aeaa9f33197 100644 --- a/apps/web/src/app/tools/risk-insights/password-health.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html @@ -34,10 +34,10 @@ - {{ passwordStrengthMap.get(r.id)[0] | i18n }} + {{ r.weakPasswordDetail?.detailValue.label | i18n }} @@ -46,8 +46,8 @@ - - {{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} + + {{ "exposedXTimes" | i18n: r.exposedPasswordDetail?.exposedXTimes }} diff --git a/apps/web/src/app/tools/risk-insights/password-health.component.spec.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts similarity index 57% rename from apps/web/src/app/tools/risk-insights/password-health.component.spec.ts rename to bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts index 50295b435b2..4329cfbde14 100644 --- a/apps/web/src/app/tools/risk-insights/password-health.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts @@ -3,17 +3,11 @@ import { ActivatedRoute, convertToParamMap } from "@angular/router"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; -// eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { RiskInsightsReportService } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TableModule } from "@bitwarden/components"; -import { TableBodyDirective } from "@bitwarden/components/src/table/table.component"; - -import { LooseComponentsModule } from "../../shared"; -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; import { PasswordHealthComponent } from "./password-health.component"; @@ -25,19 +19,10 @@ describe("PasswordHealthComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PasswordHealthComponent, PipesModule, TableModule, LooseComponentsModule], - declarations: [TableBodyDirective], + declarations: [], providers: [ - { provide: CipherService, useValue: mock() }, + { provide: RiskInsightsReportService, useValue: mock() }, { provide: I18nService, useValue: mock() }, - { provide: AuditService, useValue: mock() }, - { - provide: PasswordStrengthServiceAbstraction, - useValue: mock(), - }, - { - provide: PasswordHealthService, - useValue: mock(), - }, { provide: ActivatedRoute, useValue: { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.ts new file mode 100644 index 00000000000..62d543a080d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.ts @@ -0,0 +1,70 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { RiskInsightsReportService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { CipherHealthReportDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + BadgeModule, + ContainerComponent, + TableDataSource, + TableModule, +} from "@bitwarden/components"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; +import { OrganizationBadgeModule } from "@bitwarden/web-vault/app/vault/individual-vault/organization-badge/organization-badge.module"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; + +@Component({ + standalone: true, + selector: "tools-password-health", + templateUrl: "password-health.component.html", + imports: [ + BadgeModule, + OrganizationBadgeModule, + CommonModule, + ContainerComponent, + PipesModule, + JslibModule, + HeaderModule, + TableModule, + ], +}) +export class PasswordHealthComponent implements OnInit { + passwordUseMap = new Map(); + dataSource = new TableDataSource(); + + loading = true; + + private destroyRef = inject(DestroyRef); + + constructor( + protected riskInsightsReportService: RiskInsightsReportService, + protected i18nService: I18nService, + protected activatedRoute: ActivatedRoute, + ) {} + + ngOnInit() { + this.activatedRoute.paramMap + .pipe( + takeUntilDestroyed(this.destroyRef), + map(async (params) => { + const organizationId = params.get("organizationId"); + await this.setCiphers(organizationId); + }), + ) + .subscribe(); + } + + async setCiphers(organizationId: string) { + this.dataSource.data = await firstValueFrom( + this.riskInsightsReportService.generateRawDataReport$(organizationId), + ); + this.loading = false; + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html new file mode 100644 index 00000000000..d6f945bfb92 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html @@ -0,0 +1,8 @@ +
+ +

{{ "generatingRiskInsights" | i18n }}

+
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.ts new file mode 100644 index 00000000000..1cafa62c608 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.ts @@ -0,0 +1,14 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; + +@Component({ + selector: "tools-risk-insights-loading", + standalone: true, + imports: [CommonModule, JslibModule], + templateUrl: "./risk-insights-loading.component.html", +}) +export class ApplicationsLoadingComponent { + constructor() {} +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html new file mode 100644 index 00000000000..7fe320ede6a --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html @@ -0,0 +1,57 @@ + +
{{ "accessIntelligence" | i18n }}
+

{{ "riskInsights" | i18n }}

+
+ {{ "reviewAtRiskPasswords" | i18n }} +
+
+ + {{ + "dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a") + }} + + + {{ "refresh" | i18n }} + + + + + +
+ + + + + + + + {{ "criticalApplicationsWithCount" | i18n: criticalAppsCount }} + + + + + + + + + + + + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts new file mode 100644 index 00000000000..75601994c70 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts @@ -0,0 +1,128 @@ +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, Router } from "@angular/router"; +import { Observable, EMPTY } from "rxjs"; +import { map, switchMap } from "rxjs/operators"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { RiskInsightsDataService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; +import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; + +import { AllApplicationsComponent } from "./all-applications.component"; +import { CriticalApplicationsComponent } from "./critical-applications.component"; +import { NotifiedMembersTableComponent } from "./notified-members-table.component"; +import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component"; +import { PasswordHealthMembersComponent } from "./password-health-members.component"; +import { PasswordHealthComponent } from "./password-health.component"; + +export enum RiskInsightsTabType { + AllApps = 0, + CriticalApps = 1, + NotifiedMembers = 2, +} + +@Component({ + standalone: true, + templateUrl: "./risk-insights.component.html", + imports: [ + AllApplicationsComponent, + AsyncActionsModule, + ButtonModule, + CommonModule, + CriticalApplicationsComponent, + JslibModule, + HeaderModule, + PasswordHealthComponent, + PasswordHealthMembersComponent, + PasswordHealthMembersURIComponent, + NotifiedMembersTableComponent, + TabsModule, + ], +}) +export class RiskInsightsComponent implements OnInit { + tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps; + + dataLastUpdated: Date = new Date(); + + isCriticalAppsFeatureEnabled: boolean = false; + showDebugTabs: boolean = false; + + appsCount: number = 0; + criticalAppsCount: number = 0; + notifiedMembersCount: number = 0; + + private organizationId: string | null = null; + private destroyRef = inject(DestroyRef); + isLoading$: Observable = new Observable(); + isRefreshing$: Observable = new Observable(); + dataLastUpdated$: Observable = new Observable(); + refetching: boolean = false; + + constructor( + private route: ActivatedRoute, + private router: Router, + private configService: ConfigService, + private dataService: RiskInsightsDataService, + ) { + this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { + this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; + }); + } + + async ngOnInit() { + this.isCriticalAppsFeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.CriticalApps, + ); + + this.showDebugTabs = devFlagEnabled("showRiskInsightsDebug"); + + this.route.paramMap + .pipe( + takeUntilDestroyed(this.destroyRef), + map((params) => params.get("organizationId")), + switchMap((orgId: string | null) => { + if (orgId) { + this.organizationId = orgId; + this.dataService.fetchApplicationsReport(orgId); + this.isLoading$ = this.dataService.isLoading$; + this.isRefreshing$ = this.dataService.isRefreshing$; + this.dataLastUpdated$ = this.dataService.dataLastUpdated$; + return this.dataService.applications$; + } else { + return EMPTY; + } + }), + ) + .subscribe({ + next: (applications: ApplicationHealthReportDetail[] | null) => { + if (applications) { + this.appsCount = applications.length; + } + }, + }); + } + + /** + * Refreshes the data by re-fetching the applications report. + * This will automatically notify child components subscribed to the RiskInsightsDataService observables. + */ + refreshData(): void { + if (this.organizationId) { + this.dataService.refreshApplicationsReport(this.organizationId); + } + } + + async onTabChange(newIndex: number): Promise { + await this.router.navigate([], { + relativeTo: this.route, + queryParams: { tabIndex: newIndex }, + queryParamsHandling: "merge", + }); + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index c547c53d739..321aae165c5 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; @@ -101,7 +103,7 @@ export class MemberAccessReportComponent implements OnInit { usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: MemberDialogTab.Role, - numConfirmedMembers: this.dataSource.data.length, + numSeatsUsed: this.dataSource.data.length, }, }); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts index 5d17f8a0174..cf2f3b6417b 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts @@ -1,4 +1,6 @@ -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessExportItem } from "../view/member-access-export.view"; import { MemberAccessReportView } from "../view/member-access-report.view"; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts index 6aab54f77d5..7d6beca48ec 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessReportApiService } from "./member-access-report-api.service"; import { memberAccessReportsMock } from "./member-access-report.mock"; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts index 443edc1d2fc..b7ff5551e2c 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common"; diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 3ccdade273e..c4304ec2bd9 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -24,6 +24,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], @@ -46,6 +47,7 @@ "../../apps/web/src/**/*.spec.ts", "../../libs/common/src/platform/services/**/*.worker.ts", - "src/**/*.stories.ts" + "src/**/*.stories.ts", + "src/**/*.spec.ts" ] } diff --git a/clients.code-workspace b/clients.code-workspace index a424f91eeb4..f7d86d2a242 100644 --- a/clients.code-workspace +++ b/clients.code-workspace @@ -65,6 +65,7 @@ "angular.enable-strict-mode-prompt": false, "typescript.preferences.importModuleSpecifier": "project-relative", "javascript.preferences.importModuleSpecifier": "project-relative", + "typescript.tsdk": "root/node_modules/typescript/lib", }, "extensions": { "recommendations": [ @@ -72,6 +73,15 @@ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "Angular.ng-template", + "nick-rudenko.back-n-forth", + "streetsidesoftware.code-spell-checker", + "MS-vsliveshare.vsliveshare", + "mhutchie.git-graph", + "donjayamanne.githistory", + "eamodio.gitlens", + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "vadimcn.vscode-lldb", ], }, } diff --git a/libs/admin-console/jest.config.js b/libs/admin-console/jest.config.js index f2a8e6458af..5131753964c 100644 --- a/libs/admin-console/jest.config.js +++ b/libs/admin-console/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/admin-console/src/common/collections/abstractions/collection-admin.service.ts b/libs/admin-console/src/common/collections/abstractions/collection-admin.service.ts index 6ade8c8bed6..36222b16794 100644 --- a/libs/admin-console/src/common/collections/abstractions/collection-admin.service.ts +++ b/libs/admin-console/src/common/collections/abstractions/collection-admin.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionDetailsResponse } from "@bitwarden/admin-console/common"; import { CollectionAccessSelectionView, CollectionAdminView } from "../models"; diff --git a/libs/admin-console/src/common/collections/abstractions/collection.service.ts b/libs/admin-console/src/common/collections/abstractions/collection.service.ts index 6b590bcddaf..61fc94b271c 100644 --- a/libs/admin-console/src/common/collections/abstractions/collection.service.ts +++ b/libs/admin-console/src/common/collections/abstractions/collection.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; diff --git a/libs/admin-console/src/common/collections/abstractions/vnext-collection.service.ts b/libs/admin-console/src/common/collections/abstractions/vnext-collection.service.ts index 4b5828ccf3b..e1b2a5759a1 100644 --- a/libs/admin-console/src/common/collections/abstractions/vnext-collection.service.ts +++ b/libs/admin-console/src/common/collections/abstractions/vnext-collection.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; @@ -7,8 +9,8 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CollectionData, Collection, CollectionView } from "../models"; export abstract class vNextCollectionService { - encryptedCollections$: (userId$: Observable) => Observable; - decryptedCollections$: (userId$: Observable) => Observable; + encryptedCollections$: (userId: UserId) => Observable; + decryptedCollections$: (userId: UserId) => Observable; upsert: (collection: CollectionData | CollectionData[], userId: UserId) => Promise; replace: (collections: { [id: string]: CollectionData }, userId: UserId) => Promise; /** @@ -20,7 +22,7 @@ export abstract class vNextCollectionService { * Clear decrypted and encrypted state. * Used for logging out. */ - clear: (userId: string) => Promise; + clear: (userId: UserId) => Promise; delete: (id: string | string[], userId: UserId) => Promise; encrypt: (model: CollectionView) => Promise; /** @@ -28,7 +30,7 @@ export abstract class vNextCollectionService { */ decryptMany: ( collections: Collection[], - orgKeys?: Record, + orgKeys?: Record | null, ) => Promise; /** * Transforms the input CollectionViews into TreeNodes diff --git a/libs/admin-console/src/common/collections/models/bulk-collection-access.request.ts b/libs/admin-console/src/common/collections/models/bulk-collection-access.request.ts index 35dd31c821e..27a0e652f6b 100644 --- a/libs/admin-console/src/common/collections/models/bulk-collection-access.request.ts +++ b/libs/admin-console/src/common/collections/models/bulk-collection-access.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; export class BulkCollectionAccessRequest { diff --git a/libs/admin-console/src/common/collections/models/collection-access-selection.view.ts b/libs/admin-console/src/common/collections/models/collection-access-selection.view.ts index e7dd3df8824..9f61def9b22 100644 --- a/libs/admin-console/src/common/collections/models/collection-access-selection.view.ts +++ b/libs/admin-console/src/common/collections/models/collection-access-selection.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "@bitwarden/common/models/view/view"; interface SelectionResponseLike { diff --git a/libs/admin-console/src/common/collections/models/collection-admin.view.ts b/libs/admin-console/src/common/collections/models/collection-admin.view.ts index 1cf65a2495f..cfc9996cd7a 100644 --- a/libs/admin-console/src/common/collections/models/collection-admin.view.ts +++ b/libs/admin-console/src/common/collections/models/collection-admin.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { CollectionAccessSelectionView } from "./collection-access-selection.view"; diff --git a/libs/admin-console/src/common/collections/models/collection-with-id.request.ts b/libs/admin-console/src/common/collections/models/collection-with-id.request.ts index d74aeb740ac..ca24e139517 100644 --- a/libs/admin-console/src/common/collections/models/collection-with-id.request.ts +++ b/libs/admin-console/src/common/collections/models/collection-with-id.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Collection } from "./collection"; import { CollectionRequest } from "./collection.request"; diff --git a/libs/admin-console/src/common/collections/models/collection.request.ts b/libs/admin-console/src/common/collections/models/collection.request.ts index c8b3476e6a6..4244bf1e780 100644 --- a/libs/admin-console/src/common/collections/models/collection.request.ts +++ b/libs/admin-console/src/common/collections/models/collection.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { Collection } from "./collection"; diff --git a/libs/admin-console/src/common/collections/models/collection.ts b/libs/admin-console/src/common/collections/models/collection.ts index 2fbc035b4db..f14ccb20141 100644 --- a/libs/admin-console/src/common/collections/models/collection.ts +++ b/libs/admin-console/src/common/collections/models/collection.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import Domain from "@bitwarden/common/platform/models/domain/domain-base"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { OrgKey } from "@bitwarden/common/types/key"; diff --git a/libs/admin-console/src/common/collections/models/collection.view.ts b/libs/admin-console/src/common/collections/models/collection.view.ts index 037509634b4..1ce76608df1 100644 --- a/libs/admin-console/src/common/collections/models/collection.view.ts +++ b/libs/admin-console/src/common/collections/models/collection.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts index 08518534984..6aafbaf4678 100644 --- a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.ts b/libs/admin-console/src/common/collections/services/default-collection.service.ts index c83bb6fb16d..4070c92f27c 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts index 54c4470d414..4aa54429aad 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { firstValueFrom, of, ReplaySubject } from "rxjs"; +import { first, firstValueFrom, of, ReplaySubject, takeWhile } from "rxjs"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -87,7 +87,7 @@ describe("DefaultvNextCollectionService", () => { [org2]: orgKey2, }); - const result = await firstValueFrom(collectionService.decryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.decryptedCollections$(userId)); // Assert emitted values expect(result.length).toBe(2); @@ -121,11 +121,38 @@ describe("DefaultvNextCollectionService", () => { cryptoKeys.next({}); const encryptedCollections = await firstValueFrom( - collectionService.encryptedCollections$(of(userId)), + collectionService.encryptedCollections$(userId), ); expect(encryptedCollections.length).toBe(0); }); + + it("handles undefined orgKeys", (done) => { + // Arrange test data + const org1 = Utils.newGuid() as OrganizationId; + const collection1 = collectionDataFactory(org1); + + const org2 = Utils.newGuid() as OrganizationId; + const collection2 = collectionDataFactory(org2); + + // Emit a non-null value after the first undefined value has propagated + // This will cause the collections to emit, calling done() + cryptoKeys.pipe(first()).subscribe((val) => { + cryptoKeys.next({}); + }); + + collectionService + .decryptedCollections$(userId) + .pipe(takeWhile((val) => val.length != 2)) + .subscribe({ complete: () => done() }); + + // Arrange dependencies + void setEncryptedState([collection1, collection2]).then(() => { + // Act: emit undefined + cryptoKeys.next(undefined); + keyService.activeUserOrgKeys$ = of(undefined); + }); + }); }); describe("encryptedCollections$", () => { @@ -137,7 +164,7 @@ describe("DefaultvNextCollectionService", () => { // Arrange dependencies await setEncryptedState([collection1, collection2]); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(2); expect(result).toIncludeAllPartialMembers([ @@ -156,7 +183,7 @@ describe("DefaultvNextCollectionService", () => { await setEncryptedState(null); const decryptedCollections = await firstValueFrom( - collectionService.encryptedCollections$(of(userId)), + collectionService.encryptedCollections$(userId), ); expect(decryptedCollections.length).toBe(0); }); @@ -176,7 +203,7 @@ describe("DefaultvNextCollectionService", () => { await collectionService.upsert([updatedCollection1, newCollection3], userId); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(3); expect(result).toIncludeAllPartialMembers([ { @@ -201,7 +228,7 @@ describe("DefaultvNextCollectionService", () => { await collectionService.upsert(collection1, userId); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); expect(result).toIncludeAllPartialMembers([ { @@ -224,7 +251,7 @@ describe("DefaultvNextCollectionService", () => { userId, ); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); expect(result).toIncludeAllPartialMembers([ { @@ -241,15 +268,11 @@ describe("DefaultvNextCollectionService", () => { await collectionService.clearDecryptedState(userId); // Encrypted state remains - const encryptedState = await firstValueFrom( - collectionService.encryptedCollections$(of(userId)), - ); + const encryptedState = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(encryptedState.length).toEqual(2); // Decrypted state is cleared - const decryptedState = await firstValueFrom( - collectionService.decryptedCollections$(of(userId)), - ); + const decryptedState = await firstValueFrom(collectionService.decryptedCollections$(userId)); expect(decryptedState.length).toEqual(0); }); @@ -260,15 +283,11 @@ describe("DefaultvNextCollectionService", () => { await collectionService.clear(userId); // Encrypted state is cleared - const encryptedState = await firstValueFrom( - collectionService.encryptedCollections$(of(userId)), - ); + const encryptedState = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(encryptedState.length).toEqual(0); // Decrypted state is cleared - const decryptedState = await firstValueFrom( - collectionService.decryptedCollections$(of(userId)), - ); + const decryptedState = await firstValueFrom(collectionService.decryptedCollections$(userId)); expect(decryptedState.length).toEqual(0); }); @@ -280,7 +299,7 @@ describe("DefaultvNextCollectionService", () => { await collectionService.delete(collection1.id, userId); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toEqual(1); expect(result[0]).toMatchObject({ id: collection2.id }); }); @@ -293,7 +312,7 @@ describe("DefaultvNextCollectionService", () => { await collectionService.delete([collection1.id, collection3.id], userId); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toEqual(1); expect(result[0]).toMatchObject({ id: collection2.id }); }); @@ -304,7 +323,7 @@ describe("DefaultvNextCollectionService", () => { await collectionService.delete(collection1.id, userId); - const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId))); + const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toEqual(0); }); }); diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts index 8ca1ab7fcf0..2d5a083592b 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts @@ -1,4 +1,6 @@ -import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { combineLatest, filter, firstValueFrom, map } from "rxjs"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -28,9 +30,8 @@ export class DefaultvNextCollectionService implements vNextCollectionService { protected stateProvider: StateProvider, ) {} - encryptedCollections$(userId$: Observable) { - return userId$.pipe( - switchMap((userId) => this.encryptedState(userId).state$), + encryptedCollections$(userId: UserId) { + return this.encryptedState(userId).state$.pipe( map((collections) => { if (collections == null) { return []; @@ -41,11 +42,8 @@ export class DefaultvNextCollectionService implements vNextCollectionService { ); } - decryptedCollections$(userId$: Observable) { - return userId$.pipe( - switchMap((userId) => this.decryptedState(userId).state$), - map((collections) => collections ?? []), - ); + decryptedCollections$(userId: UserId) { + return this.decryptedState(userId).state$.pipe(map((collections) => collections ?? [])); } async upsert(toUpdate: CollectionData | CollectionData[], userId: UserId): Promise { @@ -76,14 +74,14 @@ export class DefaultvNextCollectionService implements vNextCollectionService { throw new Error("User ID is required."); } - await this.decryptedState(userId).forceValue(null); + await this.decryptedState(userId).forceValue([]); } async clear(userId: UserId): Promise { await this.encryptedState(userId).update(() => null); // This will propagate from the encrypted state update, but by doing it explicitly // the promise doesn't resolve until the update is complete. - await this.decryptedState(userId).forceValue(null); + await this.decryptedState(userId).forceValue([]); } async delete(id: CollectionId | CollectionId[], userId: UserId): Promise { @@ -123,7 +121,7 @@ export class DefaultvNextCollectionService implements vNextCollectionService { // See https://bitwarden.atlassian.net/browse/PM-12375 async decryptMany( collections: Collection[], - orgKeys?: Record, + orgKeys?: Record | null, ): Promise { if (collections == null || collections.length === 0) { return []; @@ -151,7 +149,7 @@ export class DefaultvNextCollectionService implements vNextCollectionService { collectionCopy.id = c.id; collectionCopy.organizationId = c.organizationId; const parts = c.name != null ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; - ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter); + ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, undefined, NestingDelimiter); }); return nodes; } @@ -179,14 +177,14 @@ export class DefaultvNextCollectionService implements vNextCollectionService { * @returns a SingleUserState for decrypted collection data. */ private decryptedState(userId: UserId): DerivedState { - const encryptedCollectionsWithKeys = this.encryptedState(userId).combinedState$.pipe( - switchMap(([userId, collectionData]) => - combineLatest([of(collectionData), this.keyService.orgKeys$(userId)]), - ), - ); + const encryptedCollectionsWithKeys$ = combineLatest([ + this.encryptedCollections$(userId), + // orgKeys$ can emit null during brief moments on unlock and lock/logout, we want to ignore those intermediate states + this.keyService.orgKeys$(userId).pipe(filter((orgKeys) => orgKeys != null)), + ]); return this.stateProvider.getDerived( - encryptedCollectionsWithKeys, + encryptedCollectionsWithKeys$, DECRYPTED_COLLECTION_DATA_KEY, { collectionService: this, diff --git a/libs/admin-console/src/common/collections/services/vnext-collection.state.ts b/libs/admin-console/src/common/collections/services/vnext-collection.state.ts index 533308f3cc7..331c80436f7 100644 --- a/libs/admin-console/src/common/collections/services/vnext-collection.state.ts +++ b/libs/admin-console/src/common/collections/services/vnext-collection.state.ts @@ -21,7 +21,7 @@ export const ENCRYPTED_COLLECTION_DATA_KEY = UserKeyDefinition.record, Record], + [Collection[], Record | null], CollectionView[], { collectionService: vNextCollectionService } >(COLLECTION_DATA, "decryptedCollections", { @@ -31,7 +31,6 @@ export const DECRYPTED_COLLECTION_DATA_KEY = new DeriveDefinition< return []; } - const data = Object.values(collections).map((c) => new Collection(c)); - return await collectionService.decryptMany(data, orgKeys); + return await collectionService.decryptMany(collections, orgKeys); }, }); diff --git a/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts index 42cbe1438d1..3186bdaa84b 100644 --- a/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts +++ b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts @@ -282,4 +282,15 @@ export abstract class OrganizationUserApiService { * @param id - Organization user identifier */ abstract deleteOrganizationUser(organizationId: string, id: string): Promise; + + /** + * Delete many organization users + * @param organizationId - Identifier for the organization the users belongs to + * @param ids - List of organization user identifiers to delete + * @return List of user ids, including both those that were successfully deleted and those that had an error + */ + abstract deleteManyOrganizationUsers( + organizationId: string, + ids: string[], + ): Promise>; } diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts index 20d87a774e6..9aec866c603 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; export class OrganizationUserAcceptInitRequest { diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept.request.ts index e2aada131fb..1f94374694c 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class OrganizationUserAcceptRequest { token: string; // Used to auto-enroll in master password reset diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-confirm.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-confirm.request.ts index abd487495f1..62988801424 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-confirm.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-confirm.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class OrganizationUserConfirmRequest { key: string; } diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts index 1793beccbef..9eef5027dc0 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts index 4526b227d92..f106438d440 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; export class OrganizationUserResetPasswordEnrollmentRequest extends SecretVerificationRequest { diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts index b0c4e483b95..7d060e3390e 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class OrganizationUserResetPasswordRequest { newMasterPasswordHash: string; key: string; diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts index 283af4f081f..b93c7c5cb89 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; diff --git a/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts index f61d9325c2a..1e426696d92 100644 --- a/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts +++ b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts @@ -5,7 +5,7 @@ import { import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { SelectionReadOnlyResponse } from "@bitwarden/common/admin-console/models/response/selection-read-only.response"; import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { KdfType } from "@bitwarden/common/platform/enums"; +import { KdfType } from "@bitwarden/key-management"; export class OrganizationUserResponse extends BaseResponse { id: string; diff --git a/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts index d9e069dc934..7289f41d7e7 100644 --- a/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts +++ b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts @@ -369,4 +369,18 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer false, ); } + + async deleteManyOrganizationUsers( + organizationId: string, + ids: string[], + ): Promise> { + const r = await this.apiService.send( + "DELETE", + "/organizations/" + organizationId + "/users/delete-account", + new OrganizationUserBulkRequest(ids), + true, + true, + ); + return new ListResponse(r, OrganizationUserBulkResponse); + } } diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 6004a56fb55..3d22cb2ec51 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/key-management": ["../key-management/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/angular/jest.config.js b/libs/angular/jest.config.js index c8e748575c0..5e73614eb8e 100644 --- a/libs/angular/jest.config.js +++ b/libs/angular/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 304ff4411cb..0b19935985a 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; diff --git a/libs/angular/src/admin-console/validators/not-allowed-value-async.validator.ts b/libs/angular/src/admin-console/validators/not-allowed-value-async.validator.ts index 9108ba61393..775ca591bf2 100644 --- a/libs/angular/src/admin-console/validators/not-allowed-value-async.validator.ts +++ b/libs/angular/src/admin-console/validators/not-allowed-value-async.validator.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AbstractControl, AsyncValidatorFn, ValidationErrors } from "@angular/forms"; export function notAllowedValueAsync( diff --git a/libs/angular/src/auth/components/base-login-decryption-options.component.ts b/libs/angular/src/auth/components/base-login-decryption-options-v1.component.ts similarity index 98% rename from libs/angular/src/auth/components/base-login-decryption-options.component.ts rename to libs/angular/src/auth/components/base-login-decryption-options-v1.component.ts index f674a32af8b..ca3906cead3 100644 --- a/libs/angular/src/auth/components/base-login-decryption-options.component.ts +++ b/libs/angular/src/auth/components/base-login-decryption-options-v1.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; @@ -63,7 +65,7 @@ type ExistingUserUntrustedDeviceData = { type Data = NewUserData | ExistingUserUntrustedDeviceData; @Directive() -export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { +export class BaseLoginDecryptionOptionsComponentV1 implements OnInit, OnDestroy { private destroy$ = new Subject(); protected State = State; diff --git a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts b/libs/angular/src/auth/components/base-login-via-webauthn.component.ts index c2bd1becf28..1ad4829767a 100644 --- a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts +++ b/libs/angular/src/auth/components/base-login-via-webauthn.component.ts @@ -1,6 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; +import { LoginSuccessHandlerService } from "@bitwarden/auth/common"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view"; @@ -8,6 +12,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { KeyService } from "@bitwarden/key-management"; export type State = "assert" | "assertFailed"; @@ -24,6 +29,8 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { private logService: LogService, private validationService: ValidationService, private i18nService: I18nService, + private loginSuccessHandlerService: LoginSuccessHandlerService, + private keyService: KeyService, ) {} ngOnInit(): void { @@ -57,11 +64,21 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { this.i18nService.t("twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn"), ); this.currentState = "assertFailed"; - } else if (authResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) { + return; + } + + // Only run loginSuccessHandlerService if webAuthn is used for vault decryption. + const userKey = await firstValueFrom(this.keyService.userKey$(authResult.userId)); + if (userKey) { + await this.loginSuccessHandlerService.run(authResult.userId); + } + + if (authResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) { await this.router.navigate([this.forcePasswordResetRoute]); - } else { - await this.router.navigate([this.successRoute]); + return; } + + await this.router.navigate([this.successRoute]); } catch (error) { if (error instanceof ErrorResponse) { this.validationService.showError(this.i18nService.t("invalidPasskeyPleaseTryAgain")); diff --git a/libs/angular/src/auth/components/captcha-protected.component.ts b/libs/angular/src/auth/components/captcha-protected.component.ts index 7186f6c3c48..76e62a38b4d 100644 --- a/libs/angular/src/auth/components/captcha-protected.component.ts +++ b/libs/angular/src/auth/components/captcha-protected.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, Input } from "@angular/core"; import { firstValueFrom } from "rxjs"; diff --git a/libs/angular/src/auth/components/change-password.component.ts b/libs/angular/src/auth/components/change-password.component.ts index 92b34c08f4a..7f54f35cb2a 100644 --- a/libs/angular/src/auth/components/change-password.component.ts +++ b/libs/angular/src/auth/components/change-password.component.ts @@ -1,12 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnDestroy, OnInit } from "@angular/core"; import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; 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"; @@ -16,7 +16,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { UserKey, MasterKey } from "@bitwarden/common/types/key"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { PasswordColorText } from "../../tools/password-strength/password-strength.component"; diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index fa81f1d2c01..c984b7f0cba 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { animate, state, style, transition, trigger } from "@angular/animations"; import { ConnectedPosition } from "@angular/cdk/overlay"; import { Component, EventEmitter, Output, Input, OnInit, OnDestroy } from "@angular/core"; diff --git a/libs/angular/src/auth/components/environment.component.ts b/libs/angular/src/auth/components/environment.component.ts index 25f10553308..315eb756574 100644 --- a/libs/angular/src/auth/components/environment.component.ts +++ b/libs/angular/src/auth/components/environment.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Output } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; diff --git a/libs/angular/src/auth/components/hint.component.ts b/libs/angular/src/auth/components/hint.component.ts index f7ae1e4c182..09648314a9c 100644 --- a/libs/angular/src/auth/components/hint.component.ts +++ b/libs/angular/src/auth/components/hint.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts deleted file mode 100644 index ce410029853..00000000000 --- a/libs/angular/src/auth/components/lock.component.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom, Subject } from "rxjs"; -import { concatMap, map, take, takeUntil } from "rxjs/operators"; - -import { PinServiceAbstraction, PinLockType } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { - MasterPasswordVerification, - MasterPasswordVerificationResponse, -} from "@bitwarden/common/auth/types/verification"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { 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 { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService, BiometricStateService, BiometricsService } from "@bitwarden/key-management"; - -@Directive() -export class LockComponent implements OnInit, OnDestroy { - masterPassword = ""; - pin = ""; - showPassword = false; - email: string; - pinEnabled = false; - masterPasswordEnabled = false; - webVaultHostname = ""; - formPromise: Promise; - supportsBiometric: boolean; - biometricLock: boolean; - - private activeUserId: UserId; - protected successRoute = "vault"; - protected forcePasswordResetRoute = "update-temp-password"; - protected onSuccessfulSubmit: () => Promise; - - private invalidPinAttempts = 0; - private pinLockType: PinLockType; - - private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions = undefined; - - private destroy$ = new Subject(); - - constructor( - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected router: Router, - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - protected messagingService: MessagingService, - protected keyService: KeyService, - protected vaultTimeoutService: VaultTimeoutService, - protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, - protected environmentService: EnvironmentService, - protected stateService: StateService, - protected apiService: ApiService, - protected logService: LogService, - protected ngZone: NgZone, - protected policyApiService: PolicyApiServiceAbstraction, - protected policyService: InternalPolicyService, - protected passwordStrengthService: PasswordStrengthServiceAbstraction, - protected dialogService: DialogService, - protected deviceTrustService: DeviceTrustServiceAbstraction, - protected userVerificationService: UserVerificationService, - protected pinService: PinServiceAbstraction, - protected biometricStateService: BiometricStateService, - protected biometricsService: BiometricsService, - protected accountService: AccountService, - protected authService: AuthService, - protected kdfConfigService: KdfConfigService, - protected syncService: SyncService, - protected toastService: ToastService, - ) {} - - async ngOnInit() { - this.accountService.activeAccount$ - .pipe( - concatMap(async (account) => { - this.activeUserId = account?.id; - await this.load(account?.id); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async submit() { - if (this.pinEnabled) { - return await this.handlePinRequiredUnlock(); - } - - await this.handleMasterPasswordRequiredUnlock(); - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - acceptButtonText: { key: "logOut" }, - type: "warning", - }); - - if (confirmed) { - this.messagingService.send("logout", { userId: this.activeUserId }); - } - } - - async unlockBiometric(): Promise { - if (!this.biometricLock) { - return; - } - - await this.biometricStateService.setUserPromptCancelled(); - const userKey = await this.keyService.getUserKeyFromStorage( - KeySuffixOptions.Biometric, - this.activeUserId, - ); - - if (userKey) { - await this.setUserKeyAndContinue(userKey, this.activeUserId, false); - } - - return !!userKey; - } - - async isBiometricUnlockAvailable(): Promise { - if (!(await this.biometricsService.supportsBiometric())) { - return false; - } - return this.biometricsService.isBiometricUnlockAvailable(); - } - - togglePassword() { - this.showPassword = !this.showPassword; - const input = document.getElementById(this.pinEnabled ? "pin" : "masterPassword"); - if (this.ngZone.isStable) { - input.focus(); - } else { - this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus()); - } - } - - private async handlePinRequiredUnlock() { - if (this.pin == null || this.pin === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("pinRequired"), - }); - return; - } - - return await this.doUnlockWithPin(); - } - - private async doUnlockWithPin() { - const MAX_INVALID_PIN_ENTRY_ATTEMPTS = 5; - - try { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - const userKey = await this.pinService.decryptUserKeyWithPin(this.pin, userId); - - if (userKey) { - await this.setUserKeyAndContinue(userKey, userId); - return; // successfully unlocked - } - - // Failure state: invalid PIN or failed decryption - this.invalidPinAttempts++; - - // Log user out if they have entered an invalid PIN too many times - if (this.invalidPinAttempts >= MAX_INVALID_PIN_ENTRY_ATTEMPTS) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("tooManyInvalidPinEntryAttemptsLoggingOut"), - }); - this.messagingService.send("logout"); - return; - } - - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidPin"), - }); - } catch { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("unexpectedError"), - }); - } - } - - private async handleMasterPasswordRequiredUnlock() { - if (this.masterPassword == null || this.masterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return; - } - await this.doUnlockWithMasterPassword(); - } - - private async doUnlockWithMasterPassword() { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - - const verification = { - type: VerificationType.MasterPassword, - secret: this.masterPassword, - } as MasterPasswordVerification; - - let passwordValid = false; - let response: MasterPasswordVerificationResponse; - try { - this.formPromise = this.userVerificationService.verifyUserByMasterPassword( - verification, - userId, - this.email, - ); - response = await this.formPromise; - this.enforcedMasterPasswordOptions = MasterPasswordPolicyOptions.fromResponse( - response.policyOptions, - ); - passwordValid = true; - } catch (e) { - this.logService.error(e); - } finally { - this.formPromise = null; - } - - if (!passwordValid) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidMasterPassword"), - }); - return; - } - - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - response.masterKey, - userId, - ); - await this.setUserKeyAndContinue(userKey, userId, true); - } - - private async setUserKeyAndContinue( - key: UserKey, - userId: UserId, - evaluatePasswordAfterUnlock = false, - ) { - await this.keyService.setUserKey(key, userId); - - // Now that we have a decrypted user key in memory, we can check if we - // need to establish trust on the current device - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); - - await this.doContinue(evaluatePasswordAfterUnlock); - } - - private async doContinue(evaluatePasswordAfterUnlock: boolean) { - await this.biometricStateService.resetUserPromptCancelled(); - this.messagingService.send("unlocked"); - - if (evaluatePasswordAfterUnlock) { - try { - // If we do not have any saved policies, attempt to load them from the service - if (this.enforcedMasterPasswordOptions == undefined) { - this.enforcedMasterPasswordOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$(), - ); - } - - if (this.requirePasswordChange()) { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.WeakMasterPassword, - userId, - ); - // 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([this.forcePasswordResetRoute]); - return; - } - } catch (e) { - // Do not prevent unlock if there is an error evaluating policies - this.logService.error(e); - } - } - - // Vault can be de-synced since notifications get ignored while locked. Need to check whether sync is required using the sync service. - await this.syncService.fullSync(false); - - if (this.onSuccessfulSubmit != null) { - await this.onSuccessfulSubmit(); - } else if (this.router != null) { - // 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([this.successRoute]); - } - } - - private async load(userId: UserId) { - this.pinLockType = await this.pinService.getPinLockType(userId); - - this.pinEnabled = await this.pinService.isPinDecryptionAvailable(userId); - - this.masterPasswordEnabled = await this.userVerificationService.hasMasterPassword(); - - this.supportsBiometric = await this.biometricsService.supportsBiometric(); - this.biometricLock = - (await this.vaultTimeoutSettingsService.isBiometricLockSet()) && - ((await this.keyService.hasUserKeyStored(KeySuffixOptions.Biometric)) || - !this.platformUtilsService.supportsSecureStorage()); - this.email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - - this.webVaultHostname = (await this.environmentService.getEnvironment()).getHostname(); - } - - /** - * Checks if the master password meets the enforced policy requirements - * If not, returns false - */ - private requirePasswordChange(): boolean { - if ( - this.enforcedMasterPasswordOptions == undefined || - !this.enforcedMasterPasswordOptions.enforceOnLogin - ) { - return false; - } - - const passwordStrength = this.passwordStrengthService.getPasswordStrength( - this.masterPassword, - this.email, - )?.score; - - return !this.policyService.evaluateMasterPassword( - passwordStrength, - this.masterPassword, - this.enforcedMasterPasswordOptions, - ); - } -} diff --git a/libs/angular/src/auth/components/login-v1.component.ts b/libs/angular/src/auth/components/login-v1.component.ts index 31145191898..ffe1dda3aed 100644 --- a/libs/angular/src/auth/components/login-v1.component.ts +++ b/libs/angular/src/auth/components/login-v1.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, NavigationSkipped, Router } from "@angular/router"; @@ -395,6 +397,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni email, deviceIdentifier, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.showLoginWithDevice = false; } diff --git a/libs/angular/src/auth/components/login-via-auth-request.component.ts b/libs/angular/src/auth/components/login-via-auth-request-v1.component.ts similarity index 98% rename from libs/angular/src/auth/components/login-via-auth-request.component.ts rename to libs/angular/src/auth/components/login-via-auth-request-v1.component.ts index b960cafe9c1..386068ff783 100644 --- a/libs/angular/src/auth/components/login-via-auth-request.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request-v1.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnDestroy, OnInit } from "@angular/core"; import { IsActiveMatchOptions, Router } from "@angular/router"; import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; @@ -18,7 +20,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { CreateAuthRequest } from "@bitwarden/common/auth/models/request/create-auth.request"; +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { HttpStatusCode } from "@bitwarden/common/enums/http-status-code.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -43,7 +45,7 @@ enum State { } @Directive() -export class LoginViaAuthRequestComponent +export class LoginViaAuthRequestComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy { @@ -51,7 +53,7 @@ export class LoginViaAuthRequestComponent userAuthNStatus: AuthenticationStatus; email: string; showResendNotification = false; - authRequest: CreateAuthRequest; + authRequest: AuthRequest; fingerprintPhrase: string; onSuccessfulLoginTwoFactorNavigate: () => Promise; onSuccessfulLogin: () => Promise; @@ -265,7 +267,7 @@ export class LoginViaAuthRequestComponent this.authRequestKeyPair.publicKey, ); - this.authRequest = new CreateAuthRequest( + this.authRequest = new AuthRequest( this.email, deviceIdentifier, publicKey, diff --git a/libs/angular/src/auth/components/register.component.ts b/libs/angular/src/auth/components/register.component.ts index 94f60ff637e..279294f4c06 100644 --- a/libs/angular/src/auth/components/register.component.ts +++ b/libs/angular/src/auth/components/register.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { AbstractControl, UntypedFormBuilder, ValidatorFn, Validators } from "@angular/forms"; import { Router } from "@angular/router"; @@ -5,7 +7,6 @@ import { Router } from "@angular/router"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { RegisterResponse } from "@bitwarden/common/auth/models/response/register.response"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; @@ -18,7 +19,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { AllValidationErrors, diff --git a/libs/angular/src/auth/components/remove-password.component.ts b/libs/angular/src/auth/components/remove-password.component.ts index 4b8e9cc52f0..74cb00a14b8 100644 --- a/libs/angular/src/auth/components/remove-password.component.ts +++ b/libs/angular/src/auth/components/remove-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index 81981de79d2..166707a19ea 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, of } from "rxjs"; @@ -15,11 +17,9 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -35,7 +35,7 @@ import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; diff --git a/libs/angular/src/auth/components/set-pin.component.ts b/libs/angular/src/auth/components/set-pin.component.ts index 1e3c1e6d564..7005f6e15c6 100644 --- a/libs/angular/src/auth/components/set-pin.component.ts +++ b/libs/angular/src/auth/components/set-pin.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { Directive, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; diff --git a/libs/angular/src/auth/components/sso.component.ts b/libs/angular/src/auth/components/sso.component.ts index ac64ae02462..8b4df78175f 100644 --- a/libs/angular/src/auth/components/sso.component.ts +++ b/libs/angular/src/auth/components/sso.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { ActivatedRoute, NavigationExtras, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts index 59359ab873f..bdf69f7420f 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Output } from "@angular/core"; diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts index 3c3443d2935..3131cc042f7 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts index 72d7e6a76c8..8f01403cdbb 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, OnInit, Output } from "@angular/core"; diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-expired.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-expired.component.ts new file mode 100644 index 00000000000..faa08cf073b --- /dev/null +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-expired.component.ts @@ -0,0 +1,25 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ButtonModule } from "@bitwarden/components"; + +/** + * This component is used to display a message to the user that their authentication session has expired. + * It provides a button to navigate to the login page. + */ +@Component({ + selector: "app-two-factor-expired", + standalone: true, + imports: [CommonModule, JslibModule, ButtonModule, RouterModule], + template: ` +

+ {{ "authenticationSessionTimedOut" | i18n }} +

+ + {{ "logIn" | i18n }} + + `, +}) +export class TwoFactorTimeoutComponent {} diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts index 7e9f6486911..ba3b645c68d 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core"; diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts index 58edeed93e9..6aca189a79e 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Inject, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; diff --git a/libs/angular/src/auth/components/two-factor-icon.component.ts b/libs/angular/src/auth/components/two-factor-icon.component.ts index 338ea25a3f3..c75078e413a 100644 --- a/libs/angular/src/auth/components/two-factor-icon.component.ts +++ b/libs/angular/src/auth/components/two-factor-icon.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { EmailIcon } from "../icons/email.icon"; diff --git a/libs/angular/src/auth/components/two-factor.component.spec.ts b/libs/angular/src/auth/components/two-factor.component.spec.ts index e21d119adf8..5a1903d6671 100644 --- a/libs/angular/src/auth/components/two-factor.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor.component.spec.ts @@ -86,9 +86,12 @@ describe("TwoFactorComponent", () => { }; let selectedUserDecryptionOptions: BehaviorSubject; + let twoFactorTimeoutSubject: BehaviorSubject; beforeEach(() => { + twoFactorTimeoutSubject = new BehaviorSubject(false); mockLoginStrategyService = mock(); + mockLoginStrategyService.twoFactorTimeout$ = twoFactorTimeoutSubject; mockRouter = mock(); mockI18nService = mock(); mockApiService = mock(); @@ -492,4 +495,10 @@ describe("TwoFactorComponent", () => { }); }); }); + + it("navigates to the timeout route when timeout expires", async () => { + twoFactorTimeoutSubject.next(true); + + expect(mockRouter.navigate).toHaveBeenCalledWith(["2fa-timeout"]); + }); }); diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index eaff9d665fd..e2b41ad086d 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -1,4 +1,7 @@ -import { Directive, Inject, OnDestroy, OnInit } from "@angular/core"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Directive, Inject, OnInit, OnDestroy } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, NavigationExtras, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { first } from "rxjs/operators"; @@ -68,6 +71,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI protected changePasswordRoute = "set-password"; protected forcePasswordResetRoute = "update-temp-password"; protected successRoute = "vault"; + protected twoFactorTimeoutRoute = "2fa-timeout"; get isDuoProvider(): boolean { return ( @@ -99,6 +103,21 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI ) { super(environmentService, i18nService, platformUtilsService, toastService); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); + + // Add subscription to twoFactorTimeout$ and navigate to twoFactorTimeoutRoute if expired + this.loginStrategyService.twoFactorTimeout$ + .pipe(takeUntilDestroyed()) + .subscribe(async (expired) => { + if (!expired) { + return; + } + + try { + await this.router.navigate([this.twoFactorTimeoutRoute]); + } catch (err) { + this.logService.error(`Failed to navigate to ${this.twoFactorTimeoutRoute} route`, err); + } + }); } async ngOnInit() { @@ -138,7 +157,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), - message: error, + message: this.i18nService.t("webauthnCancelOrTimeout"), }); }, (info: string) => { diff --git a/libs/angular/src/auth/components/update-password.component.ts b/libs/angular/src/auth/components/update-password.component.ts index bc31be283e0..1d1057d9aa6 100644 --- a/libs/angular/src/auth/components/update-password.component.ts +++ b/libs/angular/src/auth/components/update-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive } from "@angular/core"; import { Router } from "@angular/router"; @@ -5,7 +7,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; @@ -20,7 +21,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; diff --git a/libs/angular/src/auth/components/update-temp-password.component.ts b/libs/angular/src/auth/components/update-temp-password.component.ts index 2019d6f73c9..3fb1f7400ec 100644 --- a/libs/angular/src/auth/components/update-temp-password.component.ts +++ b/libs/angular/src/auth/components/update-temp-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; @@ -6,7 +8,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; @@ -25,7 +26,7 @@ import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; diff --git a/libs/angular/src/auth/components/user-verification-prompt.component.ts b/libs/angular/src/auth/components/user-verification-prompt.component.ts index 4b25a7cc0fe..ceb4eb1941c 100644 --- a/libs/angular/src/auth/components/user-verification-prompt.component.ts +++ b/libs/angular/src/auth/components/user-verification-prompt.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive } from "@angular/core"; import { FormBuilder } from "@angular/forms"; diff --git a/libs/angular/src/auth/components/user-verification.component.ts b/libs/angular/src/auth/components/user-verification.component.ts index 385c9b9acf5..7af53805a09 100644 --- a/libs/angular/src/auth/components/user-verification.component.ts +++ b/libs/angular/src/auth/components/user-verification.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { ControlValueAccessor, FormControl, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts index 6a19f1ace7b..887f528d547 100644 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts +++ b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts @@ -40,15 +40,14 @@ describe("unauthUiRefreshRedirect", () => { it("returns UrlTree when UnauthenticatedExtensionUIRefresh flag is enabled and preserves query params", async () => { configService.getFeatureFlag.mockResolvedValue(true); - const queryParams = { test: "test" }; + const urlTree = new UrlTree(); + urlTree.queryParams = { test: "test" }; const navigation: Navigation = { - extras: { - queryParams: queryParams, - }, + extras: {}, id: 0, initialUrl: new UrlTree(), - extractedUrl: new UrlTree(), + extractedUrl: urlTree, trigger: "imperative", previousNavigation: undefined, }; @@ -60,6 +59,8 @@ describe("unauthUiRefreshRedirect", () => { expect(configService.getFeatureFlag).toHaveBeenCalledWith( FeatureFlag.UnauthenticatedExtensionUIRefresh, ); - expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { queryParams }); + expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { + queryParams: urlTree.queryParams, + }); }); }); diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts index a54bad11479..2cb53d5324f 100644 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts +++ b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts @@ -17,7 +17,7 @@ export function unauthUiRefreshRedirect(redirectUrl: string): () => Promise, diff --git a/libs/angular/src/auth/guards/auth.guard.ts b/libs/angular/src/auth/guards/auth.guard.ts index 1486b9b57d8..8d20d7323da 100644 --- a/libs/angular/src/auth/guards/auth.guard.ts +++ b/libs/angular/src/auth/guards/auth.guard.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, diff --git a/libs/angular/src/auth/guards/lock.guard.ts b/libs/angular/src/auth/guards/lock.guard.ts index 8665c09b1ee..244e9935281 100644 --- a/libs/angular/src/auth/guards/lock.guard.ts +++ b/libs/angular/src/auth/guards/lock.guard.ts @@ -42,6 +42,12 @@ export function lockGuard(): CanActivateFn { const activeUser = await firstValueFrom(accountService.activeAccount$); + // If no active user, redirect to root: + // scenario context: user logs out on lock screen and app will reload lock comp without active user + if (!activeUser) { + return router.createUrlTree(["/"]); + } + const authStatus = await firstValueFrom(authService.authStatusFor$(activeUser.id)); if (authStatus !== AuthenticationStatus.Locked) { return router.createUrlTree(["/"]); diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index d3c262c4b7d..cebd81846c1 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; diff --git a/libs/angular/src/billing/components/invoices/invoices.component.ts b/libs/angular/src/billing/components/invoices/invoices.component.ts index 9949391cd89..8984c1afe65 100644 --- a/libs/angular/src/billing/components/invoices/invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/invoices.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; import { diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html index 0b041bd4c06..3f635656fb7 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html @@ -1,7 +1,7 @@
- + {{ "country" | i18n }}
- + {{ "zipPostalCode" | i18n }}
-
- - - {{ "includeVAT" | i18n }} - -
-
-
-
- - {{ "taxIdNumber" | i18n }} - - -
-
-
-
- - {{ "address1" | i18n }} - - -
-
- - {{ "address2" | i18n }} - - -
-
- - {{ "cityTown" | i18n }} - - -
-
- - {{ "stateProvince" | i18n }} - - + +
+ + {{ "address1" | i18n }} + + +
+
+ + {{ "address2" | i18n }} + + +
+
+ + {{ "cityTown" | i18n }} + + +
+
+ + {{ "stateProvince" | i18n }} + + +
+
+ + {{ "taxIdNumber" | i18n }} + + +
+
+
+
- diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index e73a6968607..13a6d2d0cc3 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -1,14 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; - -type Country = { - name: string; - value: string; - disabled: boolean; -}; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/models/domain"; @Component({ selector: "app-manage-tax-information", @@ -17,12 +15,23 @@ type Country = { export class ManageTaxInformationComponent implements OnInit, OnDestroy { @Input() startWith: TaxInformation; @Input() onSubmit?: (taxInformation: TaxInformation) => Promise; + @Input() showTaxIdField: boolean = true; + + /** + * Emits when the tax information has changed. + */ + @Output() taxInformationChanged = new EventEmitter(); + + /** + * Emits when the tax information has been updated. + */ @Output() taxInformationUpdated = new EventEmitter(); + private taxInformation: TaxInformation; + protected formGroup = this.formBuilder.group({ country: ["", Validators.required], postalCode: ["", Validators.required], - includeTaxId: false, taxId: "", line1: "", line2: "", @@ -30,16 +39,20 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { state: "", }); + protected isTaxSupported: boolean; + private destroy$ = new Subject(); - private taxInformation: TaxInformation; + protected readonly countries: CountryListItem[] = this.taxService.getCountries(); - constructor(private formBuilder: FormBuilder) {} + constructor( + private formBuilder: FormBuilder, + private taxService: TaxServiceAbstraction, + ) {} - getTaxInformation = (): TaxInformation & { includeTaxId: boolean } => ({ - ...this.taxInformation, - includeTaxId: this.formGroup.value.includeTaxId, - }); + getTaxInformation(): TaxInformation { + return this.taxInformation; + } submit = async () => { this.formGroup.markAllAsTouched(); @@ -50,23 +63,32 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { this.taxInformationUpdated.emit(); }; - touch = (): boolean => { - this.formGroup.markAllAsTouched(); - return this.formGroup.valid; - }; + validate(): boolean { + if (this.formGroup.dirty) { + this.formGroup.markAllAsTouched(); + return this.formGroup.valid; + } else { + return this.formGroup.valid; + } + } async ngOnInit() { if (this.startWith) { - this.formGroup.patchValue({ - ...this.startWith, - includeTaxId: - this.countrySupportsTax(this.startWith.country) && - (!!this.startWith.taxId || - !!this.startWith.line1 || - !!this.startWith.line2 || - !!this.startWith.city || - !!this.startWith.state), - }); + this.formGroup.controls.country.setValue(this.startWith.country); + this.formGroup.controls.postalCode.setValue(this.startWith.postalCode); + + this.isTaxSupported = + this.startWith && this.startWith.country + ? await this.taxService.isCountrySupported(this.startWith.country) + : false; + + if (this.isTaxSupported) { + this.formGroup.controls.taxId.setValue(this.startWith.taxId); + this.formGroup.controls.line1.setValue(this.startWith.line1); + this.formGroup.controls.line2.setValue(this.startWith.line2); + this.formGroup.controls.city.setValue(this.startWith.city); + this.formGroup.controls.state.setValue(this.startWith.state); + } } this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((values) => { @@ -80,354 +102,47 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { state: values.state, }; }); - } - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } + this.formGroup.controls.country.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe((country: string) => { + this.taxService + .isCountrySupported(country) + .then((isSupported) => (this.isTaxSupported = isSupported)) + .catch(() => (this.isTaxSupported = false)) + .finally(() => { + if (!this.isTaxSupported) { + this.formGroup.controls.taxId.setValue(null); + this.formGroup.controls.line1.setValue(null); + this.formGroup.controls.line2.setValue(null); + this.formGroup.controls.city.setValue(null); + this.formGroup.controls.state.setValue(null); + } + if (this.taxInformationChanged) { + this.taxInformationChanged.emit(this.taxInformation); + } + }); + }); - protected countrySupportsTax(countryCode: string) { - return this.taxSupportedCountryCodes.includes(countryCode); - } + this.formGroup.controls.postalCode.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + if (this.taxInformationChanged) { + this.taxInformationChanged.emit(this.taxInformation); + } + }); - protected get includeTaxIdIsSelected() { - return this.formGroup.value.includeTaxId; + this.formGroup.controls.taxId.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + if (this.taxInformationChanged) { + this.taxInformationChanged.emit(this.taxInformation); + } + }); } - protected get selectionSupportsAdditionalOptions() { - return ( - this.formGroup.value.country !== "US" && this.countrySupportsTax(this.formGroup.value.country) - ); + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } - - protected countries: Country[] = [ - { name: "-- Select --", value: "", disabled: false }, - { name: "United States", value: "US", disabled: false }, - { name: "China", value: "CN", disabled: false }, - { name: "France", value: "FR", disabled: false }, - { name: "Germany", value: "DE", disabled: false }, - { name: "Canada", value: "CA", disabled: false }, - { name: "United Kingdom", value: "GB", disabled: false }, - { name: "Australia", value: "AU", disabled: false }, - { name: "India", value: "IN", disabled: false }, - { name: "", value: "-", disabled: true }, - { name: "Afghanistan", value: "AF", disabled: false }, - { name: "Åland Islands", value: "AX", disabled: false }, - { name: "Albania", value: "AL", disabled: false }, - { name: "Algeria", value: "DZ", disabled: false }, - { name: "American Samoa", value: "AS", disabled: false }, - { name: "Andorra", value: "AD", disabled: false }, - { name: "Angola", value: "AO", disabled: false }, - { name: "Anguilla", value: "AI", disabled: false }, - { name: "Antarctica", value: "AQ", disabled: false }, - { name: "Antigua and Barbuda", value: "AG", disabled: false }, - { name: "Argentina", value: "AR", disabled: false }, - { name: "Armenia", value: "AM", disabled: false }, - { name: "Aruba", value: "AW", disabled: false }, - { name: "Austria", value: "AT", disabled: false }, - { name: "Azerbaijan", value: "AZ", disabled: false }, - { name: "Bahamas", value: "BS", disabled: false }, - { name: "Bahrain", value: "BH", disabled: false }, - { name: "Bangladesh", value: "BD", disabled: false }, - { name: "Barbados", value: "BB", disabled: false }, - { name: "Belarus", value: "BY", disabled: false }, - { name: "Belgium", value: "BE", disabled: false }, - { name: "Belize", value: "BZ", disabled: false }, - { name: "Benin", value: "BJ", disabled: false }, - { name: "Bermuda", value: "BM", disabled: false }, - { name: "Bhutan", value: "BT", disabled: false }, - { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, - { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, - { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, - { name: "Botswana", value: "BW", disabled: false }, - { name: "Bouvet Island", value: "BV", disabled: false }, - { name: "Brazil", value: "BR", disabled: false }, - { name: "British Indian Ocean Territory", value: "IO", disabled: false }, - { name: "Brunei Darussalam", value: "BN", disabled: false }, - { name: "Bulgaria", value: "BG", disabled: false }, - { name: "Burkina Faso", value: "BF", disabled: false }, - { name: "Burundi", value: "BI", disabled: false }, - { name: "Cambodia", value: "KH", disabled: false }, - { name: "Cameroon", value: "CM", disabled: false }, - { name: "Cape Verde", value: "CV", disabled: false }, - { name: "Cayman Islands", value: "KY", disabled: false }, - { name: "Central African Republic", value: "CF", disabled: false }, - { name: "Chad", value: "TD", disabled: false }, - { name: "Chile", value: "CL", disabled: false }, - { name: "Christmas Island", value: "CX", disabled: false }, - { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, - { name: "Colombia", value: "CO", disabled: false }, - { name: "Comoros", value: "KM", disabled: false }, - { name: "Congo", value: "CG", disabled: false }, - { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, - { name: "Cook Islands", value: "CK", disabled: false }, - { name: "Costa Rica", value: "CR", disabled: false }, - { name: "Côte d'Ivoire", value: "CI", disabled: false }, - { name: "Croatia", value: "HR", disabled: false }, - { name: "Cuba", value: "CU", disabled: false }, - { name: "Curaçao", value: "CW", disabled: false }, - { name: "Cyprus", value: "CY", disabled: false }, - { name: "Czech Republic", value: "CZ", disabled: false }, - { name: "Denmark", value: "DK", disabled: false }, - { name: "Djibouti", value: "DJ", disabled: false }, - { name: "Dominica", value: "DM", disabled: false }, - { name: "Dominican Republic", value: "DO", disabled: false }, - { name: "Ecuador", value: "EC", disabled: false }, - { name: "Egypt", value: "EG", disabled: false }, - { name: "El Salvador", value: "SV", disabled: false }, - { name: "Equatorial Guinea", value: "GQ", disabled: false }, - { name: "Eritrea", value: "ER", disabled: false }, - { name: "Estonia", value: "EE", disabled: false }, - { name: "Ethiopia", value: "ET", disabled: false }, - { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, - { name: "Faroe Islands", value: "FO", disabled: false }, - { name: "Fiji", value: "FJ", disabled: false }, - { name: "Finland", value: "FI", disabled: false }, - { name: "French Guiana", value: "GF", disabled: false }, - { name: "French Polynesia", value: "PF", disabled: false }, - { name: "French Southern Territories", value: "TF", disabled: false }, - { name: "Gabon", value: "GA", disabled: false }, - { name: "Gambia", value: "GM", disabled: false }, - { name: "Georgia", value: "GE", disabled: false }, - { name: "Ghana", value: "GH", disabled: false }, - { name: "Gibraltar", value: "GI", disabled: false }, - { name: "Greece", value: "GR", disabled: false }, - { name: "Greenland", value: "GL", disabled: false }, - { name: "Grenada", value: "GD", disabled: false }, - { name: "Guadeloupe", value: "GP", disabled: false }, - { name: "Guam", value: "GU", disabled: false }, - { name: "Guatemala", value: "GT", disabled: false }, - { name: "Guernsey", value: "GG", disabled: false }, - { name: "Guinea", value: "GN", disabled: false }, - { name: "Guinea-Bissau", value: "GW", disabled: false }, - { name: "Guyana", value: "GY", disabled: false }, - { name: "Haiti", value: "HT", disabled: false }, - { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, - { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, - { name: "Honduras", value: "HN", disabled: false }, - { name: "Hong Kong", value: "HK", disabled: false }, - { name: "Hungary", value: "HU", disabled: false }, - { name: "Iceland", value: "IS", disabled: false }, - { name: "Indonesia", value: "ID", disabled: false }, - { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, - { name: "Iraq", value: "IQ", disabled: false }, - { name: "Ireland", value: "IE", disabled: false }, - { name: "Isle of Man", value: "IM", disabled: false }, - { name: "Israel", value: "IL", disabled: false }, - { name: "Italy", value: "IT", disabled: false }, - { name: "Jamaica", value: "JM", disabled: false }, - { name: "Japan", value: "JP", disabled: false }, - { name: "Jersey", value: "JE", disabled: false }, - { name: "Jordan", value: "JO", disabled: false }, - { name: "Kazakhstan", value: "KZ", disabled: false }, - { name: "Kenya", value: "KE", disabled: false }, - { name: "Kiribati", value: "KI", disabled: false }, - { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, - { name: "Korea, Republic of", value: "KR", disabled: false }, - { name: "Kuwait", value: "KW", disabled: false }, - { name: "Kyrgyzstan", value: "KG", disabled: false }, - { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, - { name: "Latvia", value: "LV", disabled: false }, - { name: "Lebanon", value: "LB", disabled: false }, - { name: "Lesotho", value: "LS", disabled: false }, - { name: "Liberia", value: "LR", disabled: false }, - { name: "Libya", value: "LY", disabled: false }, - { name: "Liechtenstein", value: "LI", disabled: false }, - { name: "Lithuania", value: "LT", disabled: false }, - { name: "Luxembourg", value: "LU", disabled: false }, - { name: "Macao", value: "MO", disabled: false }, - { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, - { name: "Madagascar", value: "MG", disabled: false }, - { name: "Malawi", value: "MW", disabled: false }, - { name: "Malaysia", value: "MY", disabled: false }, - { name: "Maldives", value: "MV", disabled: false }, - { name: "Mali", value: "ML", disabled: false }, - { name: "Malta", value: "MT", disabled: false }, - { name: "Marshall Islands", value: "MH", disabled: false }, - { name: "Martinique", value: "MQ", disabled: false }, - { name: "Mauritania", value: "MR", disabled: false }, - { name: "Mauritius", value: "MU", disabled: false }, - { name: "Mayotte", value: "YT", disabled: false }, - { name: "Mexico", value: "MX", disabled: false }, - { name: "Micronesia, Federated States of", value: "FM", disabled: false }, - { name: "Moldova, Republic of", value: "MD", disabled: false }, - { name: "Monaco", value: "MC", disabled: false }, - { name: "Mongolia", value: "MN", disabled: false }, - { name: "Montenegro", value: "ME", disabled: false }, - { name: "Montserrat", value: "MS", disabled: false }, - { name: "Morocco", value: "MA", disabled: false }, - { name: "Mozambique", value: "MZ", disabled: false }, - { name: "Myanmar", value: "MM", disabled: false }, - { name: "Namibia", value: "NA", disabled: false }, - { name: "Nauru", value: "NR", disabled: false }, - { name: "Nepal", value: "NP", disabled: false }, - { name: "Netherlands", value: "NL", disabled: false }, - { name: "New Caledonia", value: "NC", disabled: false }, - { name: "New Zealand", value: "NZ", disabled: false }, - { name: "Nicaragua", value: "NI", disabled: false }, - { name: "Niger", value: "NE", disabled: false }, - { name: "Nigeria", value: "NG", disabled: false }, - { name: "Niue", value: "NU", disabled: false }, - { name: "Norfolk Island", value: "NF", disabled: false }, - { name: "Northern Mariana Islands", value: "MP", disabled: false }, - { name: "Norway", value: "NO", disabled: false }, - { name: "Oman", value: "OM", disabled: false }, - { name: "Pakistan", value: "PK", disabled: false }, - { name: "Palau", value: "PW", disabled: false }, - { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, - { name: "Panama", value: "PA", disabled: false }, - { name: "Papua New Guinea", value: "PG", disabled: false }, - { name: "Paraguay", value: "PY", disabled: false }, - { name: "Peru", value: "PE", disabled: false }, - { name: "Philippines", value: "PH", disabled: false }, - { name: "Pitcairn", value: "PN", disabled: false }, - { name: "Poland", value: "PL", disabled: false }, - { name: "Portugal", value: "PT", disabled: false }, - { name: "Puerto Rico", value: "PR", disabled: false }, - { name: "Qatar", value: "QA", disabled: false }, - { name: "Réunion", value: "RE", disabled: false }, - { name: "Romania", value: "RO", disabled: false }, - { name: "Russian Federation", value: "RU", disabled: false }, - { name: "Rwanda", value: "RW", disabled: false }, - { name: "Saint Barthélemy", value: "BL", disabled: false }, - { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, - { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, - { name: "Saint Lucia", value: "LC", disabled: false }, - { name: "Saint Martin (French part)", value: "MF", disabled: false }, - { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, - { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, - { name: "Samoa", value: "WS", disabled: false }, - { name: "San Marino", value: "SM", disabled: false }, - { name: "Sao Tome and Principe", value: "ST", disabled: false }, - { name: "Saudi Arabia", value: "SA", disabled: false }, - { name: "Senegal", value: "SN", disabled: false }, - { name: "Serbia", value: "RS", disabled: false }, - { name: "Seychelles", value: "SC", disabled: false }, - { name: "Sierra Leone", value: "SL", disabled: false }, - { name: "Singapore", value: "SG", disabled: false }, - { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, - { name: "Slovakia", value: "SK", disabled: false }, - { name: "Slovenia", value: "SI", disabled: false }, - { name: "Solomon Islands", value: "SB", disabled: false }, - { name: "Somalia", value: "SO", disabled: false }, - { name: "South Africa", value: "ZA", disabled: false }, - { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, - { name: "South Sudan", value: "SS", disabled: false }, - { name: "Spain", value: "ES", disabled: false }, - { name: "Sri Lanka", value: "LK", disabled: false }, - { name: "Sudan", value: "SD", disabled: false }, - { name: "Suriname", value: "SR", disabled: false }, - { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, - { name: "Swaziland", value: "SZ", disabled: false }, - { name: "Sweden", value: "SE", disabled: false }, - { name: "Switzerland", value: "CH", disabled: false }, - { name: "Syrian Arab Republic", value: "SY", disabled: false }, - { name: "Taiwan", value: "TW", disabled: false }, - { name: "Tajikistan", value: "TJ", disabled: false }, - { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, - { name: "Thailand", value: "TH", disabled: false }, - { name: "Timor-Leste", value: "TL", disabled: false }, - { name: "Togo", value: "TG", disabled: false }, - { name: "Tokelau", value: "TK", disabled: false }, - { name: "Tonga", value: "TO", disabled: false }, - { name: "Trinidad and Tobago", value: "TT", disabled: false }, - { name: "Tunisia", value: "TN", disabled: false }, - { name: "Turkey", value: "TR", disabled: false }, - { name: "Turkmenistan", value: "TM", disabled: false }, - { name: "Turks and Caicos Islands", value: "TC", disabled: false }, - { name: "Tuvalu", value: "TV", disabled: false }, - { name: "Uganda", value: "UG", disabled: false }, - { name: "Ukraine", value: "UA", disabled: false }, - { name: "United Arab Emirates", value: "AE", disabled: false }, - { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, - { name: "Uruguay", value: "UY", disabled: false }, - { name: "Uzbekistan", value: "UZ", disabled: false }, - { name: "Vanuatu", value: "VU", disabled: false }, - { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, - { name: "Viet Nam", value: "VN", disabled: false }, - { name: "Virgin Islands, British", value: "VG", disabled: false }, - { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, - { name: "Wallis and Futuna", value: "WF", disabled: false }, - { name: "Western Sahara", value: "EH", disabled: false }, - { name: "Yemen", value: "YE", disabled: false }, - { name: "Zambia", value: "ZM", disabled: false }, - { name: "Zimbabwe", value: "ZW", disabled: false }, - ]; - - private taxSupportedCountryCodes: string[] = [ - "CN", - "FR", - "DE", - "CA", - "GB", - "AU", - "IN", - "AD", - "AR", - "AT", - "BE", - "BO", - "BR", - "BG", - "CL", - "CO", - "CR", - "HR", - "CY", - "CZ", - "DK", - "DO", - "EC", - "EG", - "SV", - "EE", - "FI", - "GE", - "GR", - "HK", - "HU", - "IS", - "ID", - "IQ", - "IE", - "IL", - "IT", - "JP", - "KE", - "KR", - "LV", - "LI", - "LT", - "LU", - "MY", - "MT", - "MX", - "NL", - "NZ", - "NO", - "PE", - "PH", - "PL", - "PT", - "RO", - "RU", - "SA", - "RS", - "SG", - "SK", - "SI", - "ZA", - "ES", - "SE", - "CH", - "TW", - "TH", - "TR", - "UA", - "AE", - "UY", - "VE", - "VN", - ]; } diff --git a/libs/angular/src/components/callout.component.ts b/libs/angular/src/components/callout.component.ts index 2fd0878654d..1e5285cc3a9 100644 --- a/libs/angular/src/components/callout.component.ts +++ b/libs/angular/src/components/callout.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; diff --git a/libs/angular/src/components/modal/dynamic-modal.component.ts b/libs/angular/src/components/modal/dynamic-modal.component.ts index 7674662951e..ccbfa19868f 100644 --- a/libs/angular/src/components/modal/dynamic-modal.component.ts +++ b/libs/angular/src/components/modal/dynamic-modal.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y"; import { AfterViewInit, diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 7f11210f9ae..37dec53b9c7 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; diff --git a/libs/angular/src/directives/a11y-invalid.directive.ts b/libs/angular/src/directives/a11y-invalid.directive.ts index d6ed4398f9e..60580fcb60e 100644 --- a/libs/angular/src/directives/a11y-invalid.directive.ts +++ b/libs/angular/src/directives/a11y-invalid.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, OnDestroy, OnInit } from "@angular/core"; import { NgControl } from "@angular/forms"; import { Subscription } from "rxjs"; diff --git a/libs/angular/src/directives/a11y-title.directive.ts b/libs/angular/src/directives/a11y-title.directive.ts index ebfcbe27a15..f5f016b93c0 100644 --- a/libs/angular/src/directives/a11y-title.directive.ts +++ b/libs/angular/src/directives/a11y-title.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ diff --git a/libs/angular/src/directives/api-action.directive.ts b/libs/angular/src/directives/api-action.directive.ts index 2c14dbac327..e71af9cdffe 100644 --- a/libs/angular/src/directives/api-action.directive.ts +++ b/libs/angular/src/directives/api-action.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, Input, OnChanges } from "@angular/core"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; diff --git a/libs/angular/src/directives/box-row.directive.ts b/libs/angular/src/directives/box-row.directive.ts index 4392f3d0920..81fb93596f2 100644 --- a/libs/angular/src/directives/box-row.directive.ts +++ b/libs/angular/src/directives/box-row.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, HostListener, OnInit } from "@angular/core"; @Directive({ diff --git a/libs/angular/src/directives/copy-click.directive.ts b/libs/angular/src/directives/copy-click.directive.ts index c0b7fac02aa..ece867c09fd 100644 --- a/libs/angular/src/directives/copy-click.directive.ts +++ b/libs/angular/src/directives/copy-click.directive.ts @@ -1,9 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, HostListener, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { ToastVariant } from "@bitwarden/components/src/toast/toast.component"; +import { ToastService, ToastVariant } from "@bitwarden/components"; @Directive({ selector: "[appCopyClick]", diff --git a/libs/angular/src/directives/copy-text.directive.ts b/libs/angular/src/directives/copy-text.directive.ts index a7db244e11f..de26973e2c9 100644 --- a/libs/angular/src/directives/copy-text.directive.ts +++ b/libs/angular/src/directives/copy-text.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, HostListener, Input } from "@angular/core"; import { ClientType } from "@bitwarden/common/enums"; diff --git a/libs/angular/src/directives/fallback-src.directive.ts b/libs/angular/src/directives/fallback-src.directive.ts index 082c8534fa3..600782f3404 100644 --- a/libs/angular/src/directives/fallback-src.directive.ts +++ b/libs/angular/src/directives/fallback-src.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, HostListener, Input } from "@angular/core"; @Directive({ diff --git a/libs/angular/src/directives/if-feature.directive.ts b/libs/angular/src/directives/if-feature.directive.ts index 838bd264adb..0186592d2d0 100644 --- a/libs/angular/src/directives/if-feature.directive.ts +++ b/libs/angular/src/directives/if-feature.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, Input, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; import { AllowedFeatureFlagTypes, FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; diff --git a/libs/angular/src/directives/input-strip-spaces.directive.ts b/libs/angular/src/directives/input-strip-spaces.directive.ts index efa2ed8268e..3b8fee851c0 100644 --- a/libs/angular/src/directives/input-strip-spaces.directive.ts +++ b/libs/angular/src/directives/input-strip-spaces.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, HostListener, Self } from "@angular/core"; import { NgControl } from "@angular/forms"; diff --git a/libs/angular/src/directives/input-verbatim.directive.ts b/libs/angular/src/directives/input-verbatim.directive.ts index 6173835bc99..deecae624f1 100644 --- a/libs/angular/src/directives/input-verbatim.directive.ts +++ b/libs/angular/src/directives/input-verbatim.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ diff --git a/libs/angular/src/directives/not-premium.directive.ts b/libs/angular/src/directives/not-premium.directive.ts index 3aee9b192d2..5a1c636c009 100644 --- a/libs/angular/src/directives/not-premium.directive.ts +++ b/libs/angular/src/directives/not-premium.directive.ts @@ -1,6 +1,7 @@ import { Directive, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; /** @@ -14,11 +15,19 @@ export class NotPremiumDirective implements OnInit { private templateRef: TemplateRef, private viewContainer: ViewContainerRef, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async ngOnInit(): Promise { + const account = await firstValueFrom(this.accountService.activeAccount$); + + if (!account) { + this.viewContainer.createEmbeddedView(this.templateRef); + return; + } + const premium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ); if (premium) { diff --git a/libs/angular/src/directives/premium.directive.ts b/libs/angular/src/directives/premium.directive.ts index d475669a1ab..2188205ba65 100644 --- a/libs/angular/src/directives/premium.directive.ts +++ b/libs/angular/src/directives/premium.directive.ts @@ -1,6 +1,7 @@ import { Directive, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; -import { Subject, takeUntil } from "rxjs"; +import { of, Subject, switchMap, takeUntil } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; /** @@ -16,16 +17,24 @@ export class PremiumDirective implements OnInit, OnDestroy { private templateRef: TemplateRef, private viewContainer: ViewContainerRef, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async ngOnInit(): Promise { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.directiveIsDestroyed$)) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), + takeUntil(this.directiveIsDestroyed$), + ) .subscribe((premium: boolean) => { if (premium) { - this.viewContainer.clear(); - } else { this.viewContainer.createEmbeddedView(this.templateRef); + } else { + this.viewContainer.clear(); } }); } diff --git a/libs/angular/src/directives/text-drag.directive.ts b/libs/angular/src/directives/text-drag.directive.ts new file mode 100644 index 00000000000..443fbdac157 --- /dev/null +++ b/libs/angular/src/directives/text-drag.directive.ts @@ -0,0 +1,22 @@ +import { Directive, HostListener, Input } from "@angular/core"; + +@Directive({ + selector: "[appTextDrag]", + standalone: true, + host: { + draggable: "true", + class: "tw-cursor-move", + }, +}) +export class TextDragDirective { + @Input({ + alias: "appTextDrag", + required: true, + }) + data = ""; + + @HostListener("dragstart", ["$event"]) + onDragStart(event: DragEvent) { + event.dataTransfer?.setData("text", this.data); + } +} diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index bebac42fd83..4f5a8f6673c 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -43,6 +43,7 @@ import { LaunchClickDirective } from "./directives/launch-click.directive"; import { NotPremiumDirective } from "./directives/not-premium.directive"; import { StopClickDirective } from "./directives/stop-click.directive"; import { StopPropDirective } from "./directives/stop-prop.directive"; +import { TextDragDirective } from "./directives/text-drag.directive"; import { TrueFalseValueDirective } from "./directives/true-false-value.directive"; import { CreditCardNumberPipe } from "./pipes/credit-card-number.pipe"; import { PluralizePipe } from "./pipes/pluralize.pipe"; @@ -81,6 +82,7 @@ import { IconComponent } from "./vault/components/icon.component"; IconModule, LinkModule, IconModule, + TextDragDirective, ], declarations: [ A11yInvalidDirective, @@ -150,6 +152,7 @@ import { IconComponent } from "./vault/components/icon.component"; NoInvoicesComponent, ManageTaxInformationComponent, TwoFactorIconComponent, + TextDragDirective, ], providers: [ CreditCardNumberPipe, diff --git a/libs/angular/src/pipes/color-password-count.pipe.ts b/libs/angular/src/pipes/color-password-count.pipe.ts index 9890a3408cb..ed94e6dadff 100644 --- a/libs/angular/src/pipes/color-password-count.pipe.ts +++ b/libs/angular/src/pipes/color-password-count.pipe.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Pipe } from "@angular/core"; import { ColorPasswordPipe } from "./color-password.pipe"; diff --git a/libs/angular/src/pipes/search.pipe.ts b/libs/angular/src/pipes/search.pipe.ts index d784c54b5bb..9f640a5b48e 100644 --- a/libs/angular/src/pipes/search.pipe.ts +++ b/libs/angular/src/pipes/search.pipe.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Pipe, PipeTransform } from "@angular/core"; type PropertyValueFunction = (item: T) => { toString: () => string }; diff --git a/libs/angular/src/pipes/user-name.pipe.ts b/libs/angular/src/pipes/user-name.pipe.ts index f007f4ad873..29b884a3115 100644 --- a/libs/angular/src/pipes/user-name.pipe.ts +++ b/libs/angular/src/pipes/user-name.pipe.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Pipe, PipeTransform } from "@angular/core"; export interface User { diff --git a/libs/angular/src/platform/guard/feature-flag.guard.ts b/libs/angular/src/platform/guard/feature-flag.guard.ts index 1f82d810e36..d2ef4df8629 100644 --- a/libs/angular/src/platform/guard/feature-flag.guard.ts +++ b/libs/angular/src/platform/guard/feature-flag.guard.ts @@ -1,3 +1,5 @@ +// 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"; @@ -16,11 +18,13 @@ type FlagValue = boolean | number | string; * @param featureFlag - The feature flag to check * @param requiredFlagValue - Optional value to the feature flag must be equal to, defaults to true * @param redirectUrlOnDisabled - Optional url to redirect to if the feature flag is disabled + * @param showToast - Optional boolean to show a toast if the feature flag is disabled - defaults to true */ export const canAccessFeature = ( featureFlag: FeatureFlag, requiredFlagValue: FlagValue = true, redirectUrlOnDisabled?: string, + showToast = true, ): CanActivateFn => { return async () => { const configService = inject(ConfigService); @@ -36,11 +40,13 @@ export const canAccessFeature = ( return true; } - toastService.showToast({ - variant: "error", - title: null, - message: i18nService.t("accessDenied"), - }); + if (showToast) { + toastService.showToast({ + variant: "error", + title: null, + message: i18nService.t("accessDenied"), + }); + } if (redirectUrlOnDisabled != null) { return router.createUrlTree([redirectUrlOnDisabled]); diff --git a/libs/angular/src/platform/services/form-validation-errors.service.ts b/libs/angular/src/platform/services/form-validation-errors.service.ts index 674a5740c56..3de71917aa2 100644 --- a/libs/angular/src/platform/services/form-validation-errors.service.ts +++ b/libs/angular/src/platform/services/form-validation-errors.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UntypedFormGroup, ValidationErrors } from "@angular/forms"; import { diff --git a/libs/angular/src/platform/services/logging-error-handler.ts b/libs/angular/src/platform/services/logging-error-handler.ts index 5644272d35b..602b2af040e 100644 --- a/libs/angular/src/platform/services/logging-error-handler.ts +++ b/libs/angular/src/platform/services/logging-error-handler.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ErrorHandler, Injectable, Injector, inject } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.svg b/libs/angular/src/scss/bwicons/fonts/bwi-font.svg index 606f39e1168..c8535bebef8 100644 --- a/libs/angular/src/scss/bwicons/fonts/bwi-font.svg +++ b/libs/angular/src/scss/bwicons/fonts/bwi-font.svg @@ -119,7 +119,7 @@ - + diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.ttf b/libs/angular/src/scss/bwicons/fonts/bwi-font.ttf index 523c5233e03..9696152a498 100644 Binary files a/libs/angular/src/scss/bwicons/fonts/bwi-font.ttf and b/libs/angular/src/scss/bwicons/fonts/bwi-font.ttf differ diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.woff b/libs/angular/src/scss/bwicons/fonts/bwi-font.woff index 4eef2c86034..554375a1ce9 100644 Binary files a/libs/angular/src/scss/bwicons/fonts/bwi-font.woff and b/libs/angular/src/scss/bwicons/fonts/bwi-font.woff differ diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.woff2 b/libs/angular/src/scss/bwicons/fonts/bwi-font.woff2 index 7353bb99ef5..80544871f3a 100644 Binary files a/libs/angular/src/scss/bwicons/fonts/bwi-font.woff2 and b/libs/angular/src/scss/bwicons/fonts/bwi-font.woff2 differ diff --git a/libs/angular/src/scss/webfonts.css b/libs/angular/src/scss/webfonts.css index fe9bb7e1671..04b072e1bf1 100644 --- a/libs/angular/src/scss/webfonts.css +++ b/libs/angular/src/scss/webfonts.css @@ -1,89 +1,8 @@ @font-face { - font-family: "Open Sans"; - font-style: italic; - font-weight: 300; - font-display: auto; - src: url(webfonts/Open_Sans-italic-300.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: italic; - font-weight: 400; - font-display: auto; - src: url(webfonts/Open_Sans-italic-400.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: italic; - font-weight: 600; - font-display: auto; - src: url(webfonts/Open_Sans-italic-600.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: italic; - font-weight: 700; - font-display: auto; - src: url(webfonts/Open_Sans-italic-700.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: italic; - font-weight: 800; - font-display: auto; - src: url(webfonts/Open_Sans-italic-800.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: normal; - font-weight: 300; - font-display: auto; - src: url(webfonts/Open_Sans-normal-300.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: normal; - font-weight: 400; - font-display: auto; - src: url(webfonts/Open_Sans-normal-400.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: normal; - font-weight: 600; - font-display: auto; - src: url(webfonts/Open_Sans-normal-600.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: normal; - font-weight: 700; - font-display: auto; - src: url(webfonts/Open_Sans-normal-700.woff) format("woff"); - unicode-range: U+0-10FFFF; -} - -@font-face { - font-family: "Open Sans"; - font-style: normal; - font-weight: 800; - font-display: auto; - src: url(webfonts/Open_Sans-normal-800.woff) format("woff"); - unicode-range: U+0-10FFFF; + font-family: "DM Sans"; + src: + url("webfonts/dm-sans.woff2") format("woff2 supports variations"), + url("webfonts/dm-sans.woff2") format("woff2-variations"); + font-display: swap; + font-weight: 100 900; } diff --git a/libs/angular/src/scss/webfonts/Open_Sans-italic-300.woff b/libs/angular/src/scss/webfonts/Open_Sans-italic-300.woff deleted file mode 100644 index 8f8e97cfe0f..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-italic-300.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-italic-400.woff b/libs/angular/src/scss/webfonts/Open_Sans-italic-400.woff deleted file mode 100644 index f90b9960868..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-italic-400.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-italic-600.woff b/libs/angular/src/scss/webfonts/Open_Sans-italic-600.woff deleted file mode 100644 index c262a9c88fc..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-italic-600.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-italic-700.woff b/libs/angular/src/scss/webfonts/Open_Sans-italic-700.woff deleted file mode 100644 index 8526ac3d144..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-italic-700.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-italic-800.woff b/libs/angular/src/scss/webfonts/Open_Sans-italic-800.woff deleted file mode 100644 index c389a1256c3..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-italic-800.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-normal-300.woff b/libs/angular/src/scss/webfonts/Open_Sans-normal-300.woff deleted file mode 100644 index 9f56d335b1e..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-normal-300.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-normal-400.woff b/libs/angular/src/scss/webfonts/Open_Sans-normal-400.woff deleted file mode 100644 index 153a1d63a46..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-normal-400.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-normal-600.woff b/libs/angular/src/scss/webfonts/Open_Sans-normal-600.woff deleted file mode 100644 index 8271bb4a7a4..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-normal-600.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-normal-700.woff b/libs/angular/src/scss/webfonts/Open_Sans-normal-700.woff deleted file mode 100644 index 101c0be6e4d..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-normal-700.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/Open_Sans-normal-800.woff b/libs/angular/src/scss/webfonts/Open_Sans-normal-800.woff deleted file mode 100644 index 724c2e64bd0..00000000000 Binary files a/libs/angular/src/scss/webfonts/Open_Sans-normal-800.woff and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/dm-sans.woff2 b/libs/angular/src/scss/webfonts/dm-sans.woff2 new file mode 100644 index 00000000000..2375279dbf5 Binary files /dev/null and b/libs/angular/src/scss/webfonts/dm-sans.woff2 differ diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 86c5642a0c4..3842c3250e1 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 340e8f567cb..803808612cf 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; import { Subject } from "rxjs"; @@ -16,6 +18,9 @@ import { DefaultAnonLayoutWrapperDataService, LoginComponentService, DefaultLoginComponentService, + LoginDecryptionOptionsService, + DefaultLoginDecryptionOptionsService, + DefaultLoginApprovalComponentService, } from "@bitwarden/auth/angular"; import { AuthRequestServiceAbstraction, @@ -31,6 +36,11 @@ import { UserDecryptionOptionsServiceAbstraction, LogoutReason, RegisterRouteService, + AuthRequestApiService, + DefaultAuthRequestApiService, + DefaultLoginSuccessHandlerService, + LoginSuccessHandlerService, + LoginApprovalComponentServiceAbstraction, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -78,7 +88,6 @@ import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/aut import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction, @@ -101,7 +110,6 @@ import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; -import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; @@ -132,11 +140,13 @@ import { import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; +import { TaxService } from "@bitwarden/common/billing/services/tax.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; @@ -271,10 +281,17 @@ import { ImportServiceAbstraction, } from "@bitwarden/importer/core"; import { - KeyService as KeyServiceAbstraction, - DefaultKeyService as KeyService, + KeyService, + DefaultKeyService, BiometricStateService, DefaultBiometricStateService, + BiometricsService, + DefaultKdfConfigService, + KdfConfigService, + UserAsymmetricKeysRegenerationService, + DefaultUserAsymmetricKeysRegenerationService, + UserAsymmetricKeysRegenerationApiService, + DefaultUserAsymmetricKeysRegenerationApiService, } from "@bitwarden/key-management"; import { PasswordRepromptService } from "@bitwarden/vault"; import { @@ -286,6 +303,9 @@ import { IndividualVaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { NewDeviceVerificationNoticeService } from "../../../vault/src/services/new-device-verification-notice.service"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; import { ViewCacheService } from "../platform/abstractions/view-cache.service"; import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service"; @@ -401,7 +421,7 @@ const safeProviders: SafeProvider[] = [ deps: [ AccountServiceAbstraction, MessagingServiceAbstraction, - KeyServiceAbstraction, + KeyService, ApiServiceAbstraction, StateServiceAbstraction, TokenServiceAbstraction, @@ -413,7 +433,7 @@ const safeProviders: SafeProvider[] = [ deps: [ AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, - KeyServiceAbstraction, + KeyService, ApiServiceAbstraction, TokenServiceAbstraction, AppIdServiceAbstraction, @@ -434,24 +454,29 @@ const safeProviders: SafeProvider[] = [ GlobalStateProvider, BillingAccountProfileStateService, VaultTimeoutSettingsServiceAbstraction, - KdfConfigServiceAbstraction, + KdfConfigService, TaskSchedulerService, ], }), safeProvider({ provide: FileUploadServiceAbstraction, useClass: FileUploadService, - deps: [LogService], + deps: [LogService, ApiServiceAbstraction], }), safeProvider({ provide: CipherFileUploadServiceAbstraction, useClass: CipherFileUploadService, deps: [ApiServiceAbstraction, FileUploadServiceAbstraction], }), + safeProvider({ + provide: DomainSettingsService, + useClass: DefaultDomainSettingsService, + deps: [StateProvider, ConfigService], + }), safeProvider({ provide: CipherServiceAbstraction, useFactory: ( - keyService: KeyServiceAbstraction, + keyService: KeyService, domainSettingsService: DomainSettingsService, apiService: ApiServiceAbstraction, i18nService: I18nServiceAbstraction, @@ -481,7 +506,7 @@ const safeProviders: SafeProvider[] = [ accountService, ), deps: [ - KeyServiceAbstraction, + KeyService, DomainSettingsService, ApiServiceAbstraction, I18nServiceAbstraction, @@ -500,7 +525,7 @@ const safeProviders: SafeProvider[] = [ provide: InternalFolderService, useClass: FolderService, deps: [ - KeyServiceAbstraction, + KeyService, EncryptService, I18nServiceAbstraction, CipherServiceAbstraction, @@ -545,7 +570,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: CollectionService, useClass: DefaultCollectionService, - deps: [KeyServiceAbstraction, EncryptService, I18nServiceAbstraction, StateProvider], + deps: [KeyService, EncryptService, I18nServiceAbstraction, StateProvider], }), safeProvider({ provide: ENV_ADDITIONAL_REGIONS, @@ -590,8 +615,8 @@ const safeProviders: SafeProvider[] = [ deps: [CryptoFunctionServiceAbstraction], }), safeProvider({ - provide: KeyServiceAbstraction, - useClass: KeyService, + provide: KeyService, + useClass: DefaultKeyService, deps: [ PinServiceAbstraction, InternalMasterPasswordServiceAbstraction, @@ -603,7 +628,7 @@ const safeProviders: SafeProvider[] = [ StateServiceAbstraction, AccountServiceAbstraction, StateProvider, - KdfConfigServiceAbstraction, + KdfConfigService, ], }), safeProvider({ @@ -616,7 +641,7 @@ const safeProviders: SafeProvider[] = [ useFactory: legacyPasswordGenerationServiceFactory, deps: [ EncryptService, - KeyServiceAbstraction, + KeyService, PolicyServiceAbstraction, AccountServiceAbstraction, StateProvider, @@ -625,7 +650,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: GeneratorHistoryService, useClass: LocalGeneratorHistoryService, - deps: [EncryptService, KeyServiceAbstraction, StateProvider], + deps: [EncryptService, KeyService, StateProvider], }), safeProvider({ provide: UsernameGenerationServiceAbstraction, @@ -633,7 +658,7 @@ const safeProviders: SafeProvider[] = [ deps: [ ApiServiceAbstraction, I18nServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, PolicyServiceAbstraction, AccountServiceAbstraction, @@ -673,7 +698,7 @@ const safeProviders: SafeProvider[] = [ provide: InternalSendService, useClass: SendService, deps: [ - KeyServiceAbstraction, + KeyService, I18nServiceAbstraction, KeyGenerationServiceAbstraction, SendStateProviderAbstraction, @@ -700,7 +725,7 @@ const safeProviders: SafeProvider[] = [ DomainSettingsService, InternalFolderService, CipherServiceAbstraction, - KeyServiceAbstraction, + KeyService, CollectionService, MessagingServiceAbstraction, InternalPolicyService, @@ -733,7 +758,7 @@ const safeProviders: SafeProvider[] = [ AccountServiceAbstraction, PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, - KeyServiceAbstraction, + KeyService, TokenServiceAbstraction, PolicyServiceAbstraction, BiometricStateService, @@ -760,6 +785,7 @@ const safeProviders: SafeProvider[] = [ StateEventRunnerService, TaskSchedulerService, LogService, + BiometricsService, LOCKED_CALLBACK, LOGOUT_CALLBACK, ], @@ -806,7 +832,7 @@ const safeProviders: SafeProvider[] = [ ImportApiServiceAbstraction, I18nServiceAbstraction, CollectionService, - KeyServiceAbstraction, + KeyService, EncryptService, PinServiceAbstraction, AccountServiceAbstraction, @@ -819,10 +845,10 @@ const safeProviders: SafeProvider[] = [ FolderServiceAbstraction, CipherServiceAbstraction, PinServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, CryptoFunctionServiceAbstraction, - KdfConfigServiceAbstraction, + KdfConfigService, AccountServiceAbstraction, ], }), @@ -833,11 +859,11 @@ const safeProviders: SafeProvider[] = [ CipherServiceAbstraction, ApiServiceAbstraction, PinServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, CryptoFunctionServiceAbstraction, CollectionService, - KdfConfigServiceAbstraction, + KdfConfigService, AccountServiceAbstraction, ], }), @@ -940,7 +966,7 @@ const safeProviders: SafeProvider[] = [ deps: [ AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, - KeyServiceAbstraction, + KeyService, ApiServiceAbstraction, TokenServiceAbstraction, LogService, @@ -954,17 +980,15 @@ const safeProviders: SafeProvider[] = [ provide: UserVerificationServiceAbstraction, useClass: UserVerificationService, deps: [ - KeyServiceAbstraction, + KeyService, AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, I18nServiceAbstraction, UserVerificationApiServiceAbstraction, UserDecryptionOptionsServiceAbstraction, PinServiceAbstraction, - LogService, - VaultTimeoutSettingsServiceAbstraction, - PlatformUtilsServiceAbstraction, - KdfConfigServiceAbstraction, + KdfConfigService, + BiometricsService, ], }), safeProvider({ @@ -987,7 +1011,7 @@ const safeProviders: SafeProvider[] = [ deps: [ OrganizationApiServiceAbstraction, AccountServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, OrganizationUserApiService, I18nServiceAbstraction, @@ -1089,7 +1113,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DevicesServiceAbstraction, useClass: DevicesServiceImplementation, - deps: [DevicesApiServiceAbstraction], + deps: [DevicesApiServiceAbstraction, AppIdServiceAbstraction], }), safeProvider({ provide: DeviceTrustServiceAbstraction, @@ -1097,7 +1121,7 @@ const safeProviders: SafeProvider[] = [ deps: [ KeyGenerationServiceAbstraction, CryptoFunctionServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, AppIdServiceAbstraction, DevicesApiServiceAbstraction, @@ -1117,7 +1141,7 @@ const safeProviders: SafeProvider[] = [ AppIdServiceAbstraction, AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, ApiServiceAbstraction, StateProvider, @@ -1130,7 +1154,7 @@ const safeProviders: SafeProvider[] = [ AccountServiceAbstraction, CryptoFunctionServiceAbstraction, EncryptService, - KdfConfigServiceAbstraction, + KdfConfigService, KeyGenerationServiceAbstraction, LogService, MasterPasswordServiceAbstraction, @@ -1209,7 +1233,9 @@ const safeProviders: SafeProvider[] = [ useClass: OrganizationBillingService, deps: [ ApiServiceAbstraction, - KeyServiceAbstraction, + BillingApiServiceAbstraction, + ConfigService, + KeyService, EncryptService, I18nServiceAbstraction, OrganizationApiServiceAbstraction, @@ -1226,11 +1252,6 @@ const safeProviders: SafeProvider[] = [ useClass: BadgeSettingsService, deps: [StateProvider], }), - safeProvider({ - provide: DomainSettingsService, - useClass: DefaultDomainSettingsService, - deps: [StateProvider], - }), safeProvider({ provide: BiometricStateService, useClass: DefaultBiometricStateService, @@ -1256,10 +1277,15 @@ const safeProviders: SafeProvider[] = [ useClass: BillingApiService, deps: [ApiServiceAbstraction, LogService, ToastService], }), + safeProvider({ + provide: TaxServiceAbstraction, + useClass: TaxService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: BillingAccountProfileStateService, useClass: DefaultBillingAccountProfileStateService, - deps: [StateProvider], + deps: [StateProvider, PlatformUtilsServiceAbstraction, ApiServiceAbstraction], }), safeProvider({ provide: OrganizationManagementPreferencesService, @@ -1269,7 +1295,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: UserAutoUnlockKeyService, useClass: UserAutoUnlockKeyService, - deps: [KeyServiceAbstraction], + deps: [KeyService], }), safeProvider({ provide: ErrorHandler, @@ -1304,8 +1330,8 @@ const safeProviders: SafeProvider[] = [ deps: [ApiServiceAbstraction], }), safeProvider({ - provide: KdfConfigServiceAbstraction, - useClass: KdfConfigService, + provide: KdfConfigService, + useClass: DefaultKdfConfigService, deps: [StateProvider], }), safeProvider({ @@ -1313,10 +1339,10 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultSetPasswordJitService, deps: [ ApiServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, I18nServiceAbstraction, - KdfConfigServiceAbstraction, + KdfConfigService, InternalMasterPasswordServiceAbstraction, OrganizationApiServiceAbstraction, OrganizationUserApiService, @@ -1341,7 +1367,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: RegistrationFinishServiceAbstraction, useClass: DefaultRegistrationFinishService, - deps: [KeyServiceAbstraction, AccountApiServiceAbstraction], + deps: [KeyService, AccountApiServiceAbstraction], }), safeProvider({ provide: ViewCacheService, @@ -1367,9 +1393,8 @@ const safeProviders: SafeProvider[] = [ EnvironmentService, PlatformUtilsServiceAbstraction, AccountServiceAbstraction, - KdfConfigServiceAbstraction, - KeyServiceAbstraction, - ApiServiceAbstraction, + KdfConfigService, + KeyService, ], }), safeProvider({ @@ -1377,6 +1402,45 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultCipherAuthorizationService, deps: [CollectionService, OrganizationServiceAbstraction], }), + safeProvider({ + provide: AuthRequestApiService, + useClass: DefaultAuthRequestApiService, + deps: [ApiServiceAbstraction, LogService], + }), + safeProvider({ + provide: LoginApprovalComponentServiceAbstraction, + useClass: DefaultLoginApprovalComponentService, + deps: [], + }), + safeProvider({ + provide: LoginDecryptionOptionsService, + useClass: DefaultLoginDecryptionOptionsService, + deps: [MessagingServiceAbstraction], + }), + safeProvider(NewDeviceVerificationNoticeService), + safeProvider({ + provide: UserAsymmetricKeysRegenerationApiService, + useClass: DefaultUserAsymmetricKeysRegenerationApiService, + deps: [ApiServiceAbstraction], + }), + safeProvider({ + provide: UserAsymmetricKeysRegenerationService, + useClass: DefaultUserAsymmetricKeysRegenerationService, + deps: [ + KeyService, + CipherServiceAbstraction, + UserAsymmetricKeysRegenerationApiService, + LogService, + SdkService, + ApiServiceAbstraction, + ConfigService, + ], + }), + safeProvider({ + provide: LoginSuccessHandlerService, + useClass: DefaultLoginSuccessHandlerService, + deps: [SyncService, UserAsymmetricKeysRegenerationService], + }), ]; @NgModule({ diff --git a/libs/angular/src/services/modal.service.ts b/libs/angular/src/services/modal.service.ts index 250fb941f4b..db354a67867 100644 --- a/libs/angular/src/services/modal.service.ts +++ b/libs/angular/src/services/modal.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ComponentRef, Injectable, Injector, Type, ViewContainerRef } from "@angular/core"; import { first } from "rxjs/operators"; diff --git a/libs/angular/src/tools/generator/components/generator.component.ts b/libs/angular/src/tools/generator/components/generator.component.ts index b68e861a061..1f3c635e499 100644 --- a/libs/angular/src/tools/generator/components/generator.component.ts +++ b/libs/angular/src/tools/generator/components/generator.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject, combineLatest, firstValueFrom, Subject } from "rxjs"; diff --git a/libs/angular/src/tools/generator/components/password-generator-history.component.ts b/libs/angular/src/tools/generator/components/password-generator-history.component.ts index 5eea8cec282..2933163fce2 100644 --- a/libs/angular/src/tools/generator/components/password-generator-history.component.ts +++ b/libs/angular/src/tools/generator/components/password-generator-history.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/angular/src/tools/password-strength/password-strength-v2.component.ts b/libs/angular/src/tools/password-strength/password-strength-v2.component.ts index 425adba7ba9..8d9fc458384 100644 --- a/libs/angular/src/tools/password-strength/password-strength-v2.component.ts +++ b/libs/angular/src/tools/password-strength/password-strength-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core"; diff --git a/libs/angular/src/tools/password-strength/password-strength.component.ts b/libs/angular/src/tools/password-strength/password-strength.component.ts index 5960bdb9d0b..d23225b7c0c 100644 --- a/libs/angular/src/tools/password-strength/password-strength.component.ts +++ b/libs/angular/src/tools/password-strength/password-strength.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/angular/src/tools/send/add-edit.component.ts b/libs/angular/src/tools/send/add-edit.component.ts index a398ed1969d..aeee1fa104c 100644 --- a/libs/angular/src/tools/send/add-edit.component.ts +++ b/libs/angular/src/tools/send/add-edit.component.ts @@ -1,7 +1,17 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, firstValueFrom, takeUntil, map, BehaviorSubject, concatMap } from "rxjs"; +import { + Subject, + firstValueFrom, + takeUntil, + map, + BehaviorSubject, + concatMap, + switchMap, +} from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -195,8 +205,13 @@ export class AddEditComponent implements OnInit, OnDestroy { const env = await firstValueFrom(this.environmentService.environment$); this.sendLinkBaseUrl = env.getSendUrl(); - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.destroy$)) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntil(this.destroy$), + ) .subscribe((hasPremiumFromAnySource) => { this.canAccessPremium = hasPremiumFromAnySource; }); diff --git a/libs/angular/src/tools/send/send.component.ts b/libs/angular/src/tools/send/send.component.ts index f331a320cd3..6b7f911ed12 100644 --- a/libs/angular/src/tools/send/send.component.ts +++ b/libs/angular/src/tools/send/send.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core"; import { BehaviorSubject, diff --git a/libs/angular/src/utils/extension-refresh-redirect.spec.ts b/libs/angular/src/utils/extension-refresh-redirect.spec.ts new file mode 100644 index 00000000000..3291a4496ff --- /dev/null +++ b/libs/angular/src/utils/extension-refresh-redirect.spec.ts @@ -0,0 +1,62 @@ +import { TestBed } from "@angular/core/testing"; +import { Navigation, Router, UrlTree } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { extensionRefreshRedirect } from "./extension-refresh-redirect"; + +describe("extensionRefreshRedirect", () => { + let configService: MockProxy; + let router: MockProxy; + + beforeEach(() => { + configService = mock(); + router = mock(); + + TestBed.configureTestingModule({ + providers: [ + { provide: ConfigService, useValue: configService }, + { provide: Router, useValue: router }, + ], + }); + }); + + it("returns true when ExtensionRefresh flag is disabled", async () => { + configService.getFeatureFlag.mockResolvedValue(false); + + const result = await TestBed.runInInjectionContext(() => + extensionRefreshRedirect("/redirect")(), + ); + + expect(result).toBe(true); + expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh); + expect(router.parseUrl).not.toHaveBeenCalled(); + }); + + it("returns UrlTree when ExtensionRefresh flag is enabled and preserves query params", async () => { + configService.getFeatureFlag.mockResolvedValue(true); + + const urlTree = new UrlTree(); + urlTree.queryParams = { test: "test" }; + + const navigation: Navigation = { + extras: {}, + id: 0, + initialUrl: new UrlTree(), + extractedUrl: urlTree, + trigger: "imperative", + previousNavigation: undefined, + }; + + router.getCurrentNavigation.mockReturnValue(navigation); + + await TestBed.runInInjectionContext(() => extensionRefreshRedirect("/redirect")()); + + expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh); + expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { + queryParams: urlTree.queryParams, + }); + }); +}); diff --git a/libs/angular/src/utils/extension-refresh-redirect.ts b/libs/angular/src/utils/extension-refresh-redirect.ts index 81c50ceca1c..2baa3a3ec89 100644 --- a/libs/angular/src/utils/extension-refresh-redirect.ts +++ b/libs/angular/src/utils/extension-refresh-redirect.ts @@ -16,7 +16,7 @@ export function extensionRefreshRedirect(redirectUrl: string): () => Promise a?.id)); + get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -123,13 +128,14 @@ export class AddEditComponent implements OnInit, OnDestroy { protected policyService: PolicyService, protected logService: LogService, protected passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, - protected sendApiService: SendApiService, + protected organizationService: OrganizationService, protected dialogService: DialogService, protected win: Window, protected datePipe: DatePipe, protected configService: ConfigService, protected cipherAuthorizationService: CipherAuthorizationService, + protected toastService: ToastService, + private sdkService: SdkService, ) { this.typeOptions = [ { name: i18nService.t("typeLogin"), value: CipherType.Login }, @@ -206,7 +212,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.canUseReprompt = await this.passwordRepromptService.enabled(); const sshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem); - if (this.platformUtilsService.getClientType() == ClientType.Desktop && sshKeysEnabled) { + if (sshKeysEnabled) { this.typeOptions.push({ name: this.i18nService.t("typeSshKey"), value: CipherType.SshKey }); } } @@ -259,12 +265,10 @@ export class AddEditComponent implements OnInit, OnDestroy { const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(); + const activeUserId = await firstValueFrom(this.activeUserId$); if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -323,7 +327,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.login.fido2Credentials = null; } - this.folders$ = this.folderService.folderViews$; + this.folders$ = this.folderService.folderViews$(activeUserId); if (this.editMode && this.previousCipherId !== this.cipherId) { void this.eventCollectionService.collectMany(EventType.Cipher_ClientViewed, [this.cipher]); @@ -339,6 +343,17 @@ export class AddEditComponent implements OnInit, OnDestroy { [this.collectionId as CollectionId], this.isAdminConsoleAction, ); + + if (!this.editMode || this.cloneMode) { + // Creating an ssh key directly while filtering to the ssh key category + // must force a key to be set. SSH keys must never be created with an empty private key field + if ( + this.cipher.type === CipherType.SshKey && + (this.cipher.sshKey.privateKey == null || this.cipher.sshKey.privateKey === "") + ) { + await this.generateSshKey(false); + } + } } async submit(): Promise { @@ -699,7 +714,6 @@ export class AddEditComponent implements OnInit, OnDestroy { } protected saveCipher(cipher: Cipher) { - const isNotClone = this.editMode && !this.cloneMode; let orgAdmin = this.organization?.canEditAllCiphers; // if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection @@ -709,19 +723,30 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.cipher.id == null ? this.cipherService.createWithServer(cipher, orgAdmin) - : this.cipherService.updateWithServer(cipher, orgAdmin, isNotClone); + : this.cipherService.updateWithServer(cipher, orgAdmin); } protected deleteCipher() { - const asAdmin = this.organization?.canEditAllCiphers || !this.cipher.collectionIds; return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id, asAdmin) - : this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin); + ? this.cipherService.deleteWithServer(this.cipher.id, this.asAdmin) + : this.cipherService.softDeleteWithServer(this.cipher.id, this.asAdmin); } protected restoreCipher() { - const asAdmin = this.organization?.canEditAllCiphers; - return this.cipherService.restoreWithServer(this.cipher.id, asAdmin); + return this.cipherService.restoreWithServer(this.cipher.id, this.asAdmin); + } + + /** + * Determines if a cipher must be deleted as an admin by belonging to an organization and being unassigned to a collection. + */ + get asAdmin(): boolean { + return ( + this.cipher.organizationId !== null && + this.cipher.organizationId.length > 0 && + (this.organization?.canEditAllCiphers || + !this.cipher.collectionIds || + this.cipher.collectionIds.length === 0) + ); } get defaultOwnerId(): string | null { @@ -776,4 +801,26 @@ export class AddEditComponent implements OnInit, OnDestroy { return true; } + + private async generateSshKey(showNotification: boolean = true) { + await firstValueFrom(this.sdkService.client$); + const sshKey = generate_ssh_key("Ed25519"); + this.cipher.sshKey.privateKey = sshKey.private_key; + this.cipher.sshKey.publicKey = sshKey.public_key; + this.cipher.sshKey.keyFingerprint = sshKey.key_fingerprint; + + if (showNotification) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("sshKeyGenerated"), + }); + } + } + + async typeChange() { + if (this.cipher.type === CipherType.SshKey) { + await this.generateSshKey(); + } + } } diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 879cb9b4d54..1a4c428aae2 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; @@ -190,6 +192,8 @@ export class AttachmentsComponent implements OnInit { title: null, message: this.i18nService.t("fileSavedToDevice"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } @@ -207,7 +211,7 @@ export class AttachmentsComponent implements OnInit { ); const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; @@ -283,6 +287,8 @@ export class AttachmentsComponent implements OnInit { this.i18nService.t("attachmentSaved"), ); this.onReuploadedAttachment.emit(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/angular/src/vault/components/folder-add-edit.component.ts b/libs/angular/src/vault/components/folder-add-edit.component.ts index 71d7b32bafb..205733ba48d 100644 --- a/libs/angular/src/vault/components/folder-add-edit.component.ts +++ b/libs/angular/src/vault/components/folder-add-edit.component.ts @@ -1,6 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Validators, FormBuilder } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -25,6 +27,8 @@ export class FolderAddEditComponent implements OnInit { deletePromise: Promise; protected componentName = ""; + protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + formGroup = this.formBuilder.group({ name: ["", [Validators.required]], }); @@ -57,10 +61,10 @@ export class FolderAddEditComponent implements OnInit { } try { - const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(this.folder, userKey); - this.formPromise = this.folderApiService.save(folder); + this.formPromise = this.folderApiService.save(folder, activeUserId); await this.formPromise; this.platformUtilsService.showToast( "success", @@ -88,7 +92,8 @@ export class FolderAddEditComponent implements OnInit { } try { - this.deletePromise = this.folderApiService.delete(this.folder.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.deletePromise = this.folderApiService.delete(this.folder.id, activeUserId); await this.deletePromise; this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); this.onDeletedFolder.emit(this.folder); @@ -105,8 +110,10 @@ export class FolderAddEditComponent implements OnInit { if (this.editMode) { this.editMode = true; this.title = this.i18nService.t("editFolder"); - const folder = await this.folderService.get(this.folderId); - this.folder = await folder.decrypt(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.folder = await firstValueFrom( + this.folderService.getDecrypted$(this.folderId, activeUserId), + ); } else { this.title = this.i18nService.t("addFolder"); } diff --git a/libs/angular/src/vault/components/icon.component.html b/libs/angular/src/vault/components/icon.component.html index 976c6ea421d..f16545617c9 100644 --- a/libs/angular/src/vault/components/icon.component.html +++ b/libs/angular/src/vault/components/icon.component.html @@ -4,13 +4,13 @@ [src]="data.image" [appFallbackSrc]="data.fallbackImage" *ngIf="data.imageEnabled && data.image" - class="tw-max-h-6 tw-max-w-6 tw-rounded-md" + class="tw-h-6 tw-w-6 tw-rounded-md" alt="" decoding="async" loading="lazy" /> diff --git a/libs/angular/src/vault/components/icon.component.ts b/libs/angular/src/vault/components/icon.component.ts index a11be71638a..c30fb8a53e7 100644 --- a/libs/angular/src/vault/components/icon.component.ts +++ b/libs/angular/src/vault/components/icon.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core"; import { BehaviorSubject, diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index e2784620a26..942a34c58bb 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; diff --git a/libs/angular/src/vault/components/premium.component.ts b/libs/angular/src/vault/components/premium.component.ts index cfe31327891..8b1f215ef42 100644 --- a/libs/angular/src/vault/components/premium.component.ts +++ b/libs/angular/src/vault/components/premium.component.ts @@ -1,7 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OnInit, Directive } from "@angular/core"; -import { firstValueFrom, Observable } from "rxjs"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -28,8 +31,13 @@ export class PremiumComponent implements OnInit { protected dialogService: DialogService, private environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { - this.isPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.isPremium$ = accountService.activeAccount$.pipe( + switchMap((account) => + billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); } async ngOnInit() { diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index 20e779e77c4..4ef00e90063 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -1,5 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { BehaviorSubject, Subject, from, switchMap, takeUntil } from "rxjs"; +import { BehaviorSubject, firstValueFrom, from, Subject, switchMap, takeUntil } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -116,6 +118,12 @@ export class VaultItemsComponent implements OnInit, OnDestroy { protected async doSearch(indexedCiphers?: CipherView[]) { indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted()); + + const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$); + if (failedCiphers != null && failedCiphers.length > 0) { + indexedCiphers = [...failedCiphers, ...indexedCiphers]; + } + this.ciphers = await this.searchService.searchCiphers( this.searchText, [this.filter, this.deletedFilter], diff --git a/libs/angular/src/vault/components/view-custom-fields.component.ts b/libs/angular/src/vault/components/view-custom-fields.component.ts index 0250c44c252..d991a0260c1 100644 --- a/libs/angular/src/vault/components/view-custom-fields.component.ts +++ b/libs/angular/src/vault/components/view-custom-fields.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, Input } from "@angular/core"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 3226e1292bb..ef9aff736ed 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; import { ChangeDetectorRef, @@ -63,6 +65,7 @@ export class ViewComponent implements OnDestroy, OnInit { showPrivateKey: boolean; canAccessPremium: boolean; showPremiumRequiredTotp: boolean; + showUpgradeRequiredTotp: boolean; totpCode: string; totpCodeFormatted: string; totpDash: number; @@ -77,6 +80,8 @@ export class ViewComponent implements OnDestroy, OnInit { private previousCipherId: string; private passwordReprompted = false; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -139,32 +144,33 @@ export class ViewComponent implements OnDestroy, OnInit { this.cleanUp(); const cipher = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); this.showPremiumRequiredTotp = - this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; + this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationId; this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ this.collectionId as CollectionId, ]); + this.showUpgradeRequiredTotp = + this.cipher.login.totp && this.cipher.organizationId && !this.cipher.organizationUseTotp; + if (this.cipher.folderId) { this.folder = await ( - await firstValueFrom(this.folderService.folderViews$) + await firstValueFrom(this.folderService.folderViews$(activeUserId)) ).find((f) => f.id == this.cipher.folderId); } - if ( - this.cipher.type === CipherType.Login && - this.cipher.login.totp && - (cipher.organizationUseTotp || this.canAccessPremium) - ) { + const canGenerateTotp = this.cipher.organizationId + ? this.cipher.organizationUseTotp + : this.canAccessPremium; + + if (this.cipher.type === CipherType.Login && this.cipher.login.totp && canGenerateTotp) { await this.totpUpdateCode(); const interval = this.totpService.getTimeInterval(this.cipher.login.totp); await this.totpTick(interval); @@ -460,6 +466,8 @@ export class ViewComponent implements OnDestroy, OnInit { fileName: attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/angular/src/vault/guards/index.ts b/libs/angular/src/vault/guards/index.ts new file mode 100644 index 00000000000..001a4832372 --- /dev/null +++ b/libs/angular/src/vault/guards/index.ts @@ -0,0 +1 @@ +export * from "./new-device-verification-notice.guard"; diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts new file mode 100644 index 00000000000..ba19cf808ee --- /dev/null +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts @@ -0,0 +1,227 @@ +import { TestBed } from "@angular/core/testing"; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router"; +import { BehaviorSubject } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; +import { VaultProfileService } from "../services/vault-profile.service"; + +import { NewDeviceVerificationNoticeGuard } from "./new-device-verification-notice.guard"; + +describe("NewDeviceVerificationNoticeGuard", () => { + const _state = Object.freeze({}) as RouterStateSnapshot; + const emptyRoute = Object.freeze({ queryParams: {} }) as ActivatedRouteSnapshot; + const eightDaysAgo = new Date(); + eightDaysAgo.setDate(eightDaysAgo.getDate() - 8); + + const account = { + id: "account-id", + } as unknown as Account; + + const activeAccount$ = new BehaviorSubject(account); + + const createUrlTree = jest.fn(); + const getFeatureFlag = jest.fn().mockImplementation((key) => { + if (key === FeatureFlag.NewDeviceVerificationTemporaryDismiss) { + return Promise.resolve(true); + } + + return Promise.resolve(false); + }); + const isSelfHost = jest.fn().mockResolvedValue(false); + const getProfileTwoFactorEnabled = jest.fn().mockResolvedValue(false); + const policyAppliesToActiveUser$ = jest.fn().mockReturnValue(new BehaviorSubject(false)); + const noticeState$ = jest.fn().mockReturnValue(new BehaviorSubject(null)); + const getProfileCreationDate = jest.fn().mockResolvedValue(eightDaysAgo); + + beforeEach(() => { + getFeatureFlag.mockClear(); + isSelfHost.mockClear(); + getProfileCreationDate.mockClear(); + getProfileTwoFactorEnabled.mockClear(); + policyAppliesToActiveUser$.mockClear(); + createUrlTree.mockClear(); + + TestBed.configureTestingModule({ + providers: [ + { provide: Router, useValue: { createUrlTree } }, + { provide: ConfigService, useValue: { getFeatureFlag } }, + { provide: NewDeviceVerificationNoticeService, useValue: { noticeState$ } }, + { provide: AccountService, useValue: { activeAccount$ } }, + { provide: PlatformUtilsService, useValue: { isSelfHost } }, + { provide: PolicyService, useValue: { policyAppliesToActiveUser$ } }, + { + provide: VaultProfileService, + useValue: { getProfileCreationDate, getProfileTwoFactorEnabled }, + }, + ], + }); + }); + + function newDeviceGuard(route?: ActivatedRouteSnapshot) { + // Run the guard within injection context so `inject` works as you'd expect + // Pass state object to make TypeScript happy + return TestBed.runInInjectionContext(async () => + NewDeviceVerificationNoticeGuard(route ?? emptyRoute, _state), + ); + } + + describe("fromNewDeviceVerification", () => { + const route = { + queryParams: { fromNewDeviceVerification: "true" }, + } as unknown as ActivatedRouteSnapshot; + + it("returns `true` when `fromNewDeviceVerification` is present", async () => { + expect(await newDeviceGuard(route)).toBe(true); + }); + + it("does not execute other logic", async () => { + // `fromNewDeviceVerification` param should exit early, + // not foolproof but a quick way to test that other logic isn't executed + await newDeviceGuard(route); + + expect(getFeatureFlag).not.toHaveBeenCalled(); + expect(isSelfHost).not.toHaveBeenCalled(); + expect(getProfileTwoFactorEnabled).not.toHaveBeenCalled(); + expect(getProfileCreationDate).not.toHaveBeenCalled(); + expect(policyAppliesToActiveUser$).not.toHaveBeenCalled(); + }); + }); + + describe("missing current account", () => { + afterAll(() => { + // reset `activeAccount$` observable + activeAccount$.next(account); + }); + + it("redirects to login when account is missing", async () => { + activeAccount$.next(null); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledWith(["/login"]); + }); + }); + + it("returns `true` when 2FA is enabled", async () => { + getProfileTwoFactorEnabled.mockResolvedValueOnce(true); + + expect(await newDeviceGuard()).toBe(true); + }); + + it("returns `true` when the user is self hosted", async () => { + isSelfHost.mockReturnValueOnce(true); + + expect(await newDeviceGuard()).toBe(true); + }); + + it("returns `true` SSO is required", async () => { + policyAppliesToActiveUser$.mockReturnValueOnce(new BehaviorSubject(true)); + + expect(await newDeviceGuard()).toBe(true); + expect(policyAppliesToActiveUser$).toHaveBeenCalledWith(PolicyType.RequireSso); + }); + + it("returns `true` when the profile was created less than a week ago", async () => { + const sixDaysAgo = new Date(); + sixDaysAgo.setDate(sixDaysAgo.getDate() - 6); + + getProfileCreationDate.mockResolvedValueOnce(sixDaysAgo); + + expect(await newDeviceGuard()).toBe(true); + }); + + describe("temp flag", () => { + beforeEach(() => { + getFeatureFlag.mockImplementation((key) => { + if (key === FeatureFlag.NewDeviceVerificationTemporaryDismiss) { + return Promise.resolve(true); + } + + return Promise.resolve(false); + }); + }); + + afterAll(() => { + getFeatureFlag.mockReturnValue(false); + }); + + it("redirects to notice when the user has not dismissed it", async () => { + noticeState$.mockReturnValueOnce(new BehaviorSubject(null)); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]); + expect(noticeState$).toHaveBeenCalledWith(account.id); + }); + + it("redirects to notice when the user dismissed it more than 7 days ago", async () => { + const eighteenDaysAgo = new Date(); + eighteenDaysAgo.setDate(eighteenDaysAgo.getDate() - 18); + + noticeState$.mockReturnValueOnce( + new BehaviorSubject({ last_dismissal: eighteenDaysAgo.toISOString() }), + ); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]); + }); + + it("returns true when the user dismissed less than 7 days ago", async () => { + const fourDaysAgo = new Date(); + fourDaysAgo.setDate(fourDaysAgo.getDate() - 4); + + noticeState$.mockReturnValueOnce( + new BehaviorSubject({ last_dismissal: fourDaysAgo.toISOString() }), + ); + + expect(await newDeviceGuard()).toBe(true); + }); + }); + + describe("permanent flag", () => { + beforeEach(() => { + getFeatureFlag.mockImplementation((key) => { + if (key === FeatureFlag.NewDeviceVerificationPermanentDismiss) { + return Promise.resolve(true); + } + + return Promise.resolve(false); + }); + }); + + afterAll(() => { + getFeatureFlag.mockReturnValue(false); + }); + + it("redirects when the user has not dismissed", async () => { + noticeState$.mockReturnValueOnce(new BehaviorSubject(null)); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]); + + noticeState$.mockReturnValueOnce(new BehaviorSubject({ permanent_dismissal: null })); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledTimes(2); + expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]); + }); + + it("returns `true` when the user has dismissed", async () => { + noticeState$.mockReturnValueOnce(new BehaviorSubject({ permanent_dismissal: true })); + + expect(await newDeviceGuard()).toBe(true); + }); + }); +}); diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts new file mode 100644 index 00000000000..8b406877a12 --- /dev/null +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts @@ -0,0 +1,123 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, Router } from "@angular/router"; +import { Observable, firstValueFrom } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; +import { VaultProfileService } from "../services/vault-profile.service"; + +export const NewDeviceVerificationNoticeGuard: CanActivateFn = async ( + route: ActivatedRouteSnapshot, +) => { + const router = inject(Router); + const configService = inject(ConfigService); + const newDeviceVerificationNoticeService = inject(NewDeviceVerificationNoticeService); + const accountService = inject(AccountService); + const platformUtilsService = inject(PlatformUtilsService); + const policyService = inject(PolicyService); + const vaultProfileService = inject(VaultProfileService); + + if (route.queryParams["fromNewDeviceVerification"]) { + return true; + } + + const tempNoticeFlag = await configService.getFeatureFlag( + FeatureFlag.NewDeviceVerificationTemporaryDismiss, + ); + const permNoticeFlag = await configService.getFeatureFlag( + FeatureFlag.NewDeviceVerificationPermanentDismiss, + ); + + if (!tempNoticeFlag && !permNoticeFlag) { + return true; + } + + const currentAcct$: Observable = accountService.activeAccount$; + const currentAcct = await firstValueFrom(currentAcct$); + + if (!currentAcct) { + return router.createUrlTree(["/login"]); + } + + const has2FAEnabled = await hasATwoFactorProviderEnabled(vaultProfileService, currentAcct.id); + const isSelfHosted = await platformUtilsService.isSelfHost(); + const requiresSSO = await isSSORequired(policyService); + const isProfileLessThanWeekOld = await profileIsLessThanWeekOld( + vaultProfileService, + currentAcct.id, + ); + + // When any of the following are true, the device verification notice is + // not applicable for the user. + if (has2FAEnabled || isSelfHosted || requiresSSO || isProfileLessThanWeekOld) { + return true; + } + + const userItems$ = newDeviceVerificationNoticeService.noticeState$(currentAcct.id); + const userItems = await firstValueFrom(userItems$); + + // Show the notice when: + // - The temp notice flag is enabled + // - The user hasn't dismissed the notice or the user dismissed it more than 7 days ago + if ( + tempNoticeFlag && + (!userItems?.last_dismissal || isMoreThan7DaysAgo(userItems?.last_dismissal)) + ) { + return router.createUrlTree(["/new-device-notice"]); + } + + // Show the notice when: + // - The permanent notice flag is enabled + // - The user hasn't dismissed the notice + if (permNoticeFlag && !userItems?.permanent_dismissal) { + return router.createUrlTree(["/new-device-notice"]); + } + + return true; +}; + +/** Returns true has one 2FA provider enabled */ +async function hasATwoFactorProviderEnabled( + vaultProfileService: VaultProfileService, + userId: string, +): Promise { + return vaultProfileService.getProfileTwoFactorEnabled(userId); +} + +/** Returns true when the user's profile is less than a week old */ +async function profileIsLessThanWeekOld( + vaultProfileService: VaultProfileService, + userId: string, +): Promise { + const creationDate = await vaultProfileService.getProfileCreationDate(userId); + return !isMoreThan7DaysAgo(creationDate); +} + +/** Returns true when the user is required to login via SSO */ +async function isSSORequired(policyService: PolicyService) { + return firstValueFrom(policyService.policyAppliesToActiveUser$(PolicyType.RequireSso)); +} + +/** Returns the true when the date given is older than 7 days */ +function isMoreThan7DaysAgo(date?: string | Date): boolean { + if (!date) { + return false; + } + + const inputDate = new Date(date).getTime(); + const today = new Date().getTime(); + + const differenceInMS = today - inputDate; + const msInADay = 1000 * 60 * 60 * 24; + const differenceInDays = Math.round(differenceInMS / msInADay); + + return differenceInDays > 7; +} diff --git a/libs/angular/src/vault/services/vault-profile.service.spec.ts b/libs/angular/src/vault/services/vault-profile.service.spec.ts new file mode 100644 index 00000000000..7761503253a --- /dev/null +++ b/libs/angular/src/vault/services/vault-profile.service.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from "@angular/core/testing"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; + +import { VaultProfileService } from "./vault-profile.service"; + +describe("VaultProfileService", () => { + let service: VaultProfileService; + const userId = "profile-id"; + const hardcodedDateString = "2024-02-24T12:00:00Z"; + + const getProfile = jest.fn().mockResolvedValue({ + creationDate: hardcodedDateString, + twoFactorEnabled: true, + id: "new-user-id", + }); + + beforeEach(() => { + getProfile.mockClear(); + + TestBed.configureTestingModule({ + providers: [{ provide: ApiService, useValue: { getProfile } }], + }); + + jest.useFakeTimers(); + jest.setSystemTime(new Date("2024-02-22T00:00:00Z")); + service = TestBed.runInInjectionContext(() => new VaultProfileService()); + service["userId"] = userId; + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + describe("getProfileCreationDate", () => { + it("calls `getProfile` when stored profile date is not set", async () => { + expect(service["profileCreatedDate"]).toBeNull(); + + const date = await service.getProfileCreationDate(userId); + + expect(date.toISOString()).toBe("2024-02-24T12:00:00.000Z"); + expect(getProfile).toHaveBeenCalled(); + }); + + it("calls `getProfile` when stored profile id does not match", async () => { + service["profileCreatedDate"] = hardcodedDateString; + service["userId"] = "old-user-id"; + + const date = await service.getProfileCreationDate(userId); + + expect(date.toISOString()).toBe("2024-02-24T12:00:00.000Z"); + expect(getProfile).toHaveBeenCalled(); + }); + + it("does not call `getProfile` when the date is already stored", async () => { + service["profileCreatedDate"] = hardcodedDateString; + + const date = await service.getProfileCreationDate(userId); + + expect(date.toISOString()).toBe("2024-02-24T12:00:00.000Z"); + expect(getProfile).not.toHaveBeenCalled(); + }); + }); + + describe("getProfileTwoFactorEnabled", () => { + it("calls `getProfile` when stored 2FA property is not stored", async () => { + expect(service["profile2FAEnabled"]).toBeNull(); + + const twoFactorEnabled = await service.getProfileTwoFactorEnabled(userId); + + expect(twoFactorEnabled).toBe(true); + expect(getProfile).toHaveBeenCalled(); + }); + + it("calls `getProfile` when stored profile id does not match", async () => { + service["profile2FAEnabled"] = false; + service["userId"] = "old-user-id"; + + const twoFactorEnabled = await service.getProfileTwoFactorEnabled(userId); + + expect(twoFactorEnabled).toBe(true); + expect(getProfile).toHaveBeenCalled(); + }); + + it("does not call `getProfile` when 2FA property is already stored", async () => { + service["profile2FAEnabled"] = false; + + const twoFactorEnabled = await service.getProfileTwoFactorEnabled(userId); + + expect(twoFactorEnabled).toBe(false); + expect(getProfile).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/libs/angular/src/vault/services/vault-profile.service.ts b/libs/angular/src/vault/services/vault-profile.service.ts new file mode 100644 index 00000000000..b368a973781 --- /dev/null +++ b/libs/angular/src/vault/services/vault-profile.service.ts @@ -0,0 +1,64 @@ +import { Injectable, inject } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; + +@Injectable({ + providedIn: "root", +}) +/** + * Class to provide profile level details without having to call the API each time. + * NOTE: This is a temporary service and can be replaced once the `UnauthenticatedExtensionUIRefresh` flag goes live. + * The `UnauthenticatedExtensionUIRefresh` introduces a sync that takes place upon logging in. These details can then + * be added to account object and retrieved from there. + * TODO: PM-16202 + */ +export class VaultProfileService { + private apiService = inject(ApiService); + + private userId: string | null = null; + + /** Profile creation stored as a string. */ + private profileCreatedDate: string | null = null; + + /** True when 2FA is enabled on the profile. */ + private profile2FAEnabled: boolean | null = null; + + /** + * Returns the creation date of the profile. + * Note: `Date`s are mutable in JS, creating a new + * instance is important to avoid unwanted changes. + */ + async getProfileCreationDate(userId: string): Promise { + if (this.profileCreatedDate && userId === this.userId) { + return Promise.resolve(new Date(this.profileCreatedDate)); + } + + const profile = await this.fetchAndCacheProfile(); + + return new Date(profile.creationDate); + } + + /** + * Returns whether there is a 2FA provider on the profile. + */ + async getProfileTwoFactorEnabled(userId: string): Promise { + if (this.profile2FAEnabled !== null && userId === this.userId) { + return Promise.resolve(this.profile2FAEnabled); + } + + const profile = await this.fetchAndCacheProfile(); + + return profile.twoFactorEnabled; + } + + private async fetchAndCacheProfile(): Promise { + const profile = await this.apiService.getProfile(); + + this.userId = profile.id; + this.profileCreatedDate = profile.creationDate; + this.profile2FAEnabled = profile.twoFactorEnabled; + + return profile; + } +} diff --git a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts index 035f2c51cbc..d104026f2f6 100644 --- a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { CollectionView } from "@bitwarden/admin-console/common"; diff --git a/libs/angular/src/vault/vault-filter/components/folder-filter.component.ts b/libs/angular/src/vault/vault-filter/components/folder-filter.component.ts index 1836518942f..45605d583aa 100644 --- a/libs/angular/src/vault/vault-filter/components/folder-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/folder-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; diff --git a/libs/angular/src/vault/vault-filter/components/organization-filter.component.ts b/libs/angular/src/vault/vault-filter/components/organization-filter.component.ts index c45b42310c0..c0042dcfdff 100644 --- a/libs/angular/src/vault/vault-filter/components/organization-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/organization-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/angular/src/vault/vault-filter/components/status-filter.component.ts b/libs/angular/src/vault/vault-filter/components/status-filter.component.ts index db773825435..ba3842a6e11 100644 --- a/libs/angular/src/vault/vault-filter/components/status-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/status-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { CipherStatus } from "../models/cipher-status.model"; diff --git a/libs/angular/src/vault/vault-filter/components/type-filter.component.ts b/libs/angular/src/vault/vault-filter/components/type-filter.component.ts index 1613912d699..84cdf976309 100644 --- a/libs/angular/src/vault/vault-filter/components/type-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/type-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { CipherType } from "@bitwarden/common/vault/enums"; diff --git a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts index c32e556d908..0ce63c03f61 100644 --- a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, Observable } from "rxjs"; diff --git a/libs/angular/src/vault/vault-filter/models/dynamic-tree-node.model.ts b/libs/angular/src/vault/vault-filter/models/dynamic-tree-node.model.ts index 593df96f170..8bb25cc712d 100644 --- a/libs/angular/src/vault/vault-filter/models/dynamic-tree-node.model.ts +++ b/libs/angular/src/vault/vault-filter/models/dynamic-tree-node.model.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; export class DynamicTreeNode { diff --git a/libs/angular/src/vault/vault-filter/models/top-level-tree-node.model.ts b/libs/angular/src/vault/vault-filter/models/top-level-tree-node.model.ts index c3176d4191a..0e99f94f974 100644 --- a/libs/angular/src/vault/vault-filter/models/top-level-tree-node.model.ts +++ b/libs/angular/src/vault/vault-filter/models/top-level-tree-node.model.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; export type TopLevelTreeNodeId = "vaults" | "types" | "collections" | "folders"; diff --git a/libs/angular/src/vault/vault-filter/models/vault-filter.model.ts b/libs/angular/src/vault/vault-filter/models/vault-filter.model.ts index 52d7707005c..8f63c31d87a 100644 --- a/libs/angular/src/vault/vault-filter/models/vault-filter.model.ts +++ b/libs/angular/src/vault/vault-filter/models/vault-filter.model.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index 34310610ca3..260780e1964 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -1,5 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; -import { firstValueFrom, from, map, mergeMap, Observable } from "rxjs"; +import { firstValueFrom, from, map, mergeMap, Observable, switchMap } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { @@ -9,6 +11,7 @@ import { import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -19,6 +22,8 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../abstractions/deprecated-vault-filter.service"; import { DynamicTreeNode } from "../models/dynamic-tree-node.model"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { COLLAPSED_GROUPINGS } from "./../../../../../common/src/vault/services/key-state/collapsed-groupings.state"; const NestingDelimiter = "/"; @@ -30,6 +35,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti private readonly collapsedGroupings$: Observable> = this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c))); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( protected organizationService: OrganizationService, protected folderService: FolderService, @@ -37,6 +44,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti protected collectionService: CollectionService, protected policyService: PolicyService, protected stateProvider: StateProvider, + protected accountService: AccountService, ) {} async storeCollapsedFilterNodes(collapsedFilterNodes: Set): Promise { @@ -79,7 +87,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti }); }; - return this.folderService.folderViews$.pipe( + return this.activeUserId$.pipe( + switchMap((userId) => this.folderService.folderViews$(userId)), mergeMap((folders) => from(transformation(folders))), ); } @@ -124,8 +133,9 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti } async getFolderNested(id: string): Promise> { + const activeUserId = await firstValueFrom(this.activeUserId$); const folders = await this.getAllFoldersNested( - await firstValueFrom(this.folderService.folderViews$), + await firstValueFrom(this.folderService.folderViews$(activeUserId)), ); return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode; } diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index 6004a56fb55..6c510f81492 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -1,5 +1,25 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-components": ["../tools/generator/components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/importer/core": ["../importer/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault": ["../vault/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/auth/jest.config.js b/libs/auth/jest.config.js index 8bc834c7dab..121d423be17 100644 --- a/libs/auth/jest.config.js +++ b/libs/auth/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html index cfd436d93ae..95b1e6cadfe 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html @@ -4,6 +4,7 @@ [icon]="pageIcon" [showReadonlyHostname]="showReadonlyHostname" [maxWidth]="maxWidth" + [titleAreaMaxWidth]="titleAreaMaxWidth" > diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index f805da0700a..04dc3b6dfd2 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router"; import { Subject, filter, switchMap, takeUntil, tap } from "rxjs"; @@ -33,6 +35,10 @@ export interface AnonLayoutWrapperData { * Optional flag to set the max-width of the page. Defaults to 'md' if not provided. */ maxWidth?: "md" | "3xl"; + /** + * Optional flag to set the max-width of the title area. Defaults to null if not provided. + */ + titleAreaMaxWidth?: "md"; } @Component({ @@ -48,6 +54,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { protected pageIcon: Icon; protected showReadonlyHostname: boolean; protected maxWidth: "md" | "3xl"; + protected titleAreaMaxWidth: "md"; constructor( private router: Router, @@ -98,6 +105,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { this.showReadonlyHostname = Boolean(firstChildRouteData["showReadonlyHostname"]); this.maxWidth = firstChildRouteData["maxWidth"]; + this.titleAreaMaxWidth = firstChildRouteData["titleAreaMaxWidth"]; } private listenForServiceDataChanges() { @@ -155,6 +163,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { this.pageIcon = null; this.showReadonlyHostname = null; this.maxWidth = null; + this.titleAreaMaxWidth = null; } ngOnDestroy() { diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index b07504b7c8d..9f504c75d29 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -18,7 +18,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { LockIcon } from "../icons"; import { RegistrationCheckEmailIcon } from "../icons/registration-check-email.icon"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html index 8a0ac4b7186..cb3445abd96 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -1,18 +1,22 @@ -
- + -
+
@@ -36,14 +40,14 @@

[ngClass]="{ 'tw-max-w-md': maxWidth === 'md', 'tw-max-w-3xl': maxWidth === '3xl' }" >

-
+
{{ "accessing" | i18n }} {{ hostname }}
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts index 7da7ef1a7a2..05ddb9614f1 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, HostBinding, Input, OnChanges, OnInit, SimpleChanges } from "@angular/core"; import { RouterModule } from "@angular/router"; @@ -7,8 +9,14 @@ import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IconModule, Icon } from "../../../../components/src/icon"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../components/src/typography"; import { BitwardenLogo, BitwardenShield } from "../icons"; @@ -31,7 +39,14 @@ export class AnonLayoutComponent implements OnInit, OnChanges { @Input() showReadonlyHostname: boolean; @Input() hideLogo: boolean = false; @Input() hideFooter: boolean = false; - @Input() decreaseTopPadding: boolean = false; + + /** + * Max width of the title area content + * + * @default null + */ + @Input() titleAreaMaxWidth?: "md"; + /** * Max width of the layout content * @@ -58,6 +73,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges { async ngOnInit() { this.maxWidth = this.maxWidth ?? "md"; + this.titleAreaMaxWidth = this.titleAreaMaxWidth ?? null; this.hostname = (await firstValueFrom(this.environmentService.environment$)).getHostname(); this.version = await this.platformUtilsService.getApplicationVersion(); diff --git a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts index 77dc082c052..c7e15d9dcfa 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts @@ -7,7 +7,11 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service"; import { LockIcon } from "../icons"; @@ -190,3 +194,22 @@ export const HideFooter: Story = { `, }), }; + +export const WithTitleAreaMaxWidth: Story = { + render: (args) => ({ + props: { + ...args, + title: "This is a very long long title to demonstrate titleAreaMaxWidth set to 'md'", + subtitle: + "This is a very long subtitle that demonstrates how the max width container handles longer text content with the titleAreaMaxWidth input set to 'md'. Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita, quod est?", + }, + template: ` + +
+
Primary Projected Content Area (customizable)
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Necessitatibus illum vero, placeat recusandae esse ratione eius minima veniam nemo, quas beatae! Impedit molestiae alias sapiente explicabo. Sapiente corporis ipsa numquam?
+
+
+ `, + }), +}; diff --git a/libs/auth/src/angular/icons/devices.icon.ts b/libs/auth/src/angular/icons/devices.icon.ts new file mode 100644 index 00000000000..54acea5b087 --- /dev/null +++ b/libs/auth/src/angular/icons/devices.icon.ts @@ -0,0 +1,52 @@ +import { svgIcon } from "@bitwarden/components"; + +export const DevicesIcon = svgIcon` + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/index.ts b/libs/auth/src/angular/icons/index.ts index 70460a7aea4..0e86ee7fc8e 100644 --- a/libs/auth/src/angular/icons/index.ts +++ b/libs/auth/src/angular/icons/index.ts @@ -1,5 +1,6 @@ export * from "./bitwarden-logo.icon"; export * from "./bitwarden-shield.icon"; +export * from "./devices.icon"; export * from "./lock.icon"; export * from "./registration-check-email.icon"; export * from "./user-lock.icon"; @@ -9,3 +10,5 @@ export * from "./vault.icon"; export * from "./registration-user-add.icon"; export * from "./registration-lock-alt.icon"; export * from "./registration-expired-link.icon"; +export * from "./sso-key.icon"; +export * from "./two-factor-timeout.icon"; diff --git a/libs/auth/src/angular/icons/sso-key.icon.ts b/libs/auth/src/angular/icons/sso-key.icon.ts new file mode 100644 index 00000000000..38ae8a66525 --- /dev/null +++ b/libs/auth/src/angular/icons/sso-key.icon.ts @@ -0,0 +1,10 @@ +import { svgIcon } from "@bitwarden/components"; + +export const SsoKeyIcon = svgIcon` + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-timeout.icon.ts b/libs/auth/src/angular/icons/two-factor-timeout.icon.ts new file mode 100644 index 00000000000..71d0aa549dc --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-timeout.icon.ts @@ -0,0 +1,8 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorTimeoutIcon = svgIcon` + + + + +`; diff --git a/libs/auth/src/angular/icons/user-lock.icon.ts b/libs/auth/src/angular/icons/user-lock.icon.ts index fef00a09a92..e85eac6fc2d 100644 --- a/libs/auth/src/angular/icons/user-lock.icon.ts +++ b/libs/auth/src/angular/icons/user-lock.icon.ts @@ -1,22 +1,102 @@ import { svgIcon } from "@bitwarden/components"; export const UserLockIcon = svgIcon` - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + `; diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index d3d9e600918..66111f3e5af 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -24,6 +24,14 @@ export * from "./login/login-secondary-content.component"; export * from "./login/login-component.service"; export * from "./login/default-login-component.service"; +// login decryption options +export * from "./login-decryption-options/login-decryption-options.component"; +export * from "./login-decryption-options/login-decryption-options.service"; +export * from "./login-decryption-options/default-login-decryption-options.service"; + +// login via auth request +export * from "./login-via-auth-request/login-via-auth-request.component"; + // password callout export * from "./password-callout/password-callout.component"; @@ -49,12 +57,17 @@ export * from "./user-verification/user-verification-dialog.component"; export * from "./user-verification/user-verification-dialog.types"; export * from "./user-verification/user-verification-form-input.component"; -// lock -export * from "./lock/lock.component"; -export * from "./lock/lock-component.service"; - // vault timeout export * from "./vault-timeout-input/vault-timeout-input.component"; +// sso +export * from "./sso/sso.component"; +export * from "./sso/sso-component.service"; +export * from "./sso/default-sso-component.service"; + // self hosted environment configuration dialog export * from "./self-hosted-env-config-dialog/self-hosted-env-config-dialog.component"; + +// login approval +export * from "./login-approval/login-approval.component"; +export * from "./login-approval/default-login-approval-component.service"; diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index e110d2d53e3..c613cf5f533 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ReactiveFormsModule, FormBuilder, Validators } from "@angular/forms"; @@ -9,7 +11,6 @@ import { import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -23,9 +24,13 @@ import { InputModule, ToastService, } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InputsFieldMatch } from "../../../../angular/src/auth/validators/inputs-field-match.validator"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; import { PasswordCalloutComponent } from "../password-callout/password-callout.component"; diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index e483ae16b32..41577328f87 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { importProvidersFrom } from "@angular/core"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { action } from "@storybook/addon-actions"; @@ -12,7 +14,8 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { InputPasswordComponent } from "./input-password.component"; diff --git a/libs/auth/src/angular/input-password/password-input-result.ts b/libs/auth/src/angular/input-password/password-input-result.ts index 66bb338dc16..07157aaf4ca 100644 --- a/libs/auth/src/angular/input-password/password-input-result.ts +++ b/libs/auth/src/angular/input-password/password-input-result.ts @@ -1,5 +1,5 @@ -import { PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { MasterKey } from "@bitwarden/common/types/key"; +import { PBKDF2KdfConfig } from "@bitwarden/key-management"; export interface PasswordInputResult { masterKey: MasterKey; diff --git a/libs/auth/src/angular/login-approval/default-login-approval-component.service.spec.ts b/libs/auth/src/angular/login-approval/default-login-approval-component.service.spec.ts new file mode 100644 index 00000000000..ec274fac8bc --- /dev/null +++ b/libs/auth/src/angular/login-approval/default-login-approval-component.service.spec.ts @@ -0,0 +1,25 @@ +import { TestBed } from "@angular/core/testing"; + +import { DefaultLoginApprovalComponentService } from "./default-login-approval-component.service"; +import { LoginApprovalComponent } from "./login-approval.component"; + +describe("DefaultLoginApprovalComponentService", () => { + let service: DefaultLoginApprovalComponentService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [DefaultLoginApprovalComponentService], + }); + + service = TestBed.inject(DefaultLoginApprovalComponentService); + }); + + it("is created successfully", () => { + expect(service).toBeTruthy(); + }); + + it("has showLoginRequestedAlertIfWindowNotVisible method that is a no-op", async () => { + const loginApprovalComponent = {} as LoginApprovalComponent; + await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalComponent.email); + }); +}); diff --git a/libs/auth/src/angular/login-approval/default-login-approval-component.service.ts b/libs/auth/src/angular/login-approval/default-login-approval-component.service.ts new file mode 100644 index 00000000000..8b0463be6c1 --- /dev/null +++ b/libs/auth/src/angular/login-approval/default-login-approval-component.service.ts @@ -0,0 +1,16 @@ +import { LoginApprovalComponentServiceAbstraction } from "../../common/abstractions/login-approval-component.service.abstraction"; + +/** + * Default implementation of the LoginApprovalComponentServiceAbstraction. + */ +export class DefaultLoginApprovalComponentService + implements LoginApprovalComponentServiceAbstraction +{ + /** + * No-op implementation of the showLoginRequestedAlertIfWindowNotVisible method. + * @returns + */ + async showLoginRequestedAlertIfWindowNotVisible(email?: string): Promise { + return; + } +} diff --git a/libs/auth/src/angular/login-approval/login-approval.component.html b/libs/auth/src/angular/login-approval/login-approval.component.html new file mode 100644 index 00000000000..c0cb9b9caf4 --- /dev/null +++ b/libs/auth/src/angular/login-approval/login-approval.component.html @@ -0,0 +1,50 @@ + + {{ "areYouTryingtoLogin" | i18n }} + + +
+ +
+
+ + +

{{ "logInAttemptBy" | i18n: email }}

+
+ {{ "fingerprintPhraseHeader" | i18n }} +

{{ fingerprintPhrase }}

+
+
+ {{ "deviceType" | i18n }} +

{{ authRequestResponse?.requestDeviceType }}

+
+
+ {{ "ipAddress" | i18n }} +

{{ authRequestResponse?.requestIpAddress }}

+
+
+ {{ "time" | i18n }} +

{{ requestTimeText }}

+
+
+
+ + + + + diff --git a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts new file mode 100644 index 00000000000..da30df62fff --- /dev/null +++ b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts @@ -0,0 +1,126 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { + AuthRequestServiceAbstraction, + LoginApprovalComponentServiceAbstraction, +} from "@bitwarden/auth/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ToastService } from "@bitwarden/components"; +import { KeyService } from "@bitwarden/key-management"; + +import { LoginApprovalComponent } from "./login-approval.component"; + +describe("LoginApprovalComponent", () => { + let component: LoginApprovalComponent; + let fixture: ComponentFixture; + + let authRequestService: MockProxy; + let accountService: MockProxy; + let apiService: MockProxy; + let i18nService: MockProxy; + let dialogRef: MockProxy; + let toastService: MockProxy; + let validationService: MockProxy; + + const testNotificationId = "test-notification-id"; + const testEmail = "test@bitwarden.com"; + const testPublicKey = "test-public-key"; + + beforeEach(async () => { + authRequestService = mock(); + accountService = mock(); + apiService = mock(); + i18nService = mock(); + dialogRef = mock(); + toastService = mock(); + validationService = mock(); + + accountService.activeAccount$ = of({ + email: testEmail, + id: "test-user-id" as UserId, + emailVerified: true, + name: null, + }); + + await TestBed.configureTestingModule({ + imports: [LoginApprovalComponent], + providers: [ + { provide: DIALOG_DATA, useValue: { notificationId: testNotificationId } }, + { provide: AuthRequestServiceAbstraction, useValue: authRequestService }, + { provide: AccountService, useValue: accountService }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: I18nService, useValue: i18nService }, + { provide: ApiService, useValue: apiService }, + { provide: AppIdService, useValue: mock() }, + { provide: KeyService, useValue: mock() }, + { provide: DialogRef, useValue: dialogRef }, + { provide: ToastService, useValue: toastService }, + { provide: ValidationService, useValue: validationService }, + { + provide: LoginApprovalComponentServiceAbstraction, + useValue: mock(), + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(LoginApprovalComponent); + component = fixture.componentInstance; + }); + + it("creates successfully", () => { + expect(component).toBeTruthy(); + }); + + describe("ngOnInit", () => { + beforeEach(() => { + apiService.getAuthRequest.mockResolvedValue({ + publicKey: testPublicKey, + creationDate: new Date().toISOString(), + } as AuthRequestResponse); + authRequestService.getFingerprintPhrase.mockResolvedValue("test-phrase"); + }); + + it("retrieves and sets auth request data", async () => { + await component.ngOnInit(); + + expect(apiService.getAuthRequest).toHaveBeenCalledWith(testNotificationId); + expect(component.email).toBe(testEmail); + expect(component.fingerprintPhrase).toBeDefined(); + }); + + it("updates time text initially", async () => { + i18nService.t.mockReturnValue("justNow"); + + await component.ngOnInit(); + expect(component.requestTimeText).toBe("justNow"); + }); + }); + + describe("denyLogin", () => { + it("denies auth request and shows info toast", async () => { + const response = { requestApproved: false } as AuthRequestResponse; + apiService.getAuthRequest.mockResolvedValue(response); + authRequestService.approveOrDenyAuthRequest.mockResolvedValue(response); + i18nService.t.mockReturnValue("denied message"); + + await component.denyLogin(); + + expect(authRequestService.approveOrDenyAuthRequest).toHaveBeenCalledWith(false, response); + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "info", + title: null, + message: "denied message", + }); + }); + }); +}); diff --git a/apps/desktop/src/auth/login/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts similarity index 87% rename from apps/desktop/src/auth/login/login-approval.component.ts rename to libs/auth/src/angular/login-approval/login-approval.component.ts index e6428e0020c..3b44f545abb 100644 --- a/apps/desktop/src/auth/login/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -1,16 +1,22 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, OnInit, OnDestroy, Inject } from "@angular/core"; import { Subject, firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; +import { + AuthRequestServiceAbstraction, + LoginApprovalComponentServiceAbstraction as LoginApprovalComponentService, +} from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { AsyncActionsModule, @@ -35,6 +41,8 @@ export interface LoginApprovalDialogParams { imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule], }) export class LoginApprovalComponent implements OnInit, OnDestroy { + loading = true; + notificationId: string; private destroy$ = new Subject(); @@ -56,25 +64,26 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { protected keyService: KeyService, private dialogRef: DialogRef, private toastService: ToastService, + private loginApprovalComponentService: LoginApprovalComponentService, + private validationService: ValidationService, ) { this.notificationId = params.notificationId; } async ngOnDestroy(): Promise { clearInterval(this.interval); - const closedWithButton = await firstValueFrom(this.dialogRef.closed); - if (!closedWithButton) { - // 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.retrieveAuthRequestAndRespond(false); - } this.destroy$.next(); this.destroy$.complete(); } async ngOnInit() { if (this.notificationId != null) { - this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + try { + this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + } catch (error) { + this.validationService.showError(error); + } + const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey); this.email = await await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), @@ -89,14 +98,9 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { this.updateTimeText(); }, RequestTimeUpdate); - const isVisible = await ipc.platform.isWindowVisible(); - if (!isVisible) { - await ipc.auth.loginRequest( - this.i18nService.t("logInRequested"), - this.i18nService.t("confirmLoginAtemptForMail", this.email), - this.i18nService.t("close"), - ); - } + this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email); + + this.loading = false; } } @@ -132,6 +136,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { ); this.showResultToast(loginResponse); } + + this.dialogRef.close(approve); } showResultToast(loginResponse: AuthRequestResponse) { diff --git a/libs/auth/src/angular/login-decryption-options/default-login-decryption-options.service.spec.ts b/libs/auth/src/angular/login-decryption-options/default-login-decryption-options.service.spec.ts new file mode 100644 index 00000000000..735b7667540 --- /dev/null +++ b/libs/auth/src/angular/login-decryption-options/default-login-decryption-options.service.spec.ts @@ -0,0 +1,37 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; + +import { DefaultLoginDecryptionOptionsService } from "./default-login-decryption-options.service"; + +describe("DefaultLoginDecryptionOptionsService", () => { + let service: DefaultLoginDecryptionOptionsService; + + let messagingService: MockProxy; + + beforeEach(() => { + messagingService = mock(); + + service = new DefaultLoginDecryptionOptionsService(messagingService); + }); + + it("should instantiate the service", () => { + expect(service).not.toBeFalsy(); + }); + + describe("handleCreateUserSuccess()", () => { + it("should return null", async () => { + const result = await service.handleCreateUserSuccess(); + + expect(result).toBeNull(); + }); + }); + + describe("logOut()", () => { + it("should send a logout message", async () => { + await service.logOut(); + + expect(messagingService.send).toHaveBeenCalledWith("logout"); + }); + }); +}); diff --git a/libs/auth/src/angular/login-decryption-options/default-login-decryption-options.service.ts b/libs/auth/src/angular/login-decryption-options/default-login-decryption-options.service.ts new file mode 100644 index 00000000000..c5d593d4d85 --- /dev/null +++ b/libs/auth/src/angular/login-decryption-options/default-login-decryption-options.service.ts @@ -0,0 +1,17 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; + +import { LoginDecryptionOptionsService } from "./login-decryption-options.service"; + +export class DefaultLoginDecryptionOptionsService implements LoginDecryptionOptionsService { + constructor(protected messagingService: MessagingService) {} + + handleCreateUserSuccess(): Promise { + return null; + } + + async logOut(): Promise { + this.messagingService.send("logout"); + } +} diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html new file mode 100644 index 00000000000..b3d218389bf --- /dev/null +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html @@ -0,0 +1,64 @@ + +
+ + {{ "loading" | i18n }} +
+
+ +
+ + + {{ "rememberThisDevice" | i18n }} + {{ "uncheckIfPublicDevice" | i18n }} + +
+ + + + + + +
+ + + +
+ {{ "or" | i18n }} +
+
+ + + + + + +
+
diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts new file mode 100644 index 00000000000..a3f5e062e4f --- /dev/null +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -0,0 +1,317 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, FormControl, ReactiveFormsModule } from "@angular/forms"; +import { Router } from "@angular/router"; +import { catchError, defer, firstValueFrom, from, map, of, switchMap, throwError } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + LoginEmailServiceAbstraction, + UserDecryptionOptions, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; +import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { ClientType } from "@bitwarden/common/enums"; +import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; +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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { + AsyncActionsModule, + ButtonModule, + CheckboxModule, + DialogService, + FormFieldModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; +import { KeyService } from "@bitwarden/key-management"; + +import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; + +import { LoginDecryptionOptionsService } from "./login-decryption-options.service"; + +enum State { + NewUser, + ExistingUserUntrustedDevice, +} + +@Component({ + standalone: true, + templateUrl: "./login-decryption-options.component.html", + imports: [ + AsyncActionsModule, + ButtonModule, + CheckboxModule, + CommonModule, + FormFieldModule, + JslibModule, + ReactiveFormsModule, + TypographyModule, + ], +}) +export class LoginDecryptionOptionsComponent implements OnInit { + private activeAccountId: UserId; + private clientType: ClientType; + private email: string; + + protected loading = false; + protected state: State; + protected State = State; + + protected formGroup = this.formBuilder.group({ + rememberDevice: [true], // Remember device means for the user to trust the device + }); + + private get rememberDeviceControl(): FormControl { + return this.formGroup.controls.rememberDevice; + } + + // New User Properties + private newUserOrgId: string; + + // Existing User Untrusted Device Properties + protected canApproveFromOtherDevice = false; + protected canRequestAdminApproval = false; + protected canApproveWithMasterPassword = false; + + constructor( + private accountService: AccountService, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, + private apiService: ApiService, + private destroyRef: DestroyRef, + private deviceTrustService: DeviceTrustServiceAbstraction, + private dialogService: DialogService, + private formBuilder: FormBuilder, + private i18nService: I18nService, + private keyService: KeyService, + private loginDecryptionOptionsService: LoginDecryptionOptionsService, + private loginEmailService: LoginEmailServiceAbstraction, + private messagingService: MessagingService, + private organizationApiService: OrganizationApiServiceAbstraction, + private passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction, + private platformUtilsService: PlatformUtilsService, + private router: Router, + private ssoLoginService: SsoLoginServiceAbstraction, + private toastService: ToastService, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private validationService: ValidationService, + ) { + this.clientType = this.platformUtilsService.getClientType(); + } + + async ngOnInit() { + this.loading = true; + + this.activeAccountId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + + this.email = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.email)), + ); + + if (!this.email) { + await this.handleMissingEmail(); + return; + } + + this.observeAndPersistRememberDeviceValueChanges(); + await this.setRememberDeviceDefaultValueFromState(); + + try { + const userDecryptionOptions = await firstValueFrom( + this.userDecryptionOptionsService.userDecryptionOptions$, + ); + + if ( + !userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval && + !userDecryptionOptions?.hasMasterPassword + ) { + /** + * We are dealing with a new account if both are true: + * - User does NOT have admin approval (i.e. has not enrolled in admin reset) + * - User does NOT have a master password + */ + await this.loadNewUserData(); + } else { + this.loadExistingUserUntrustedDeviceData(userDecryptionOptions); + } + } catch (err) { + this.validationService.showError(err); + } finally { + this.loading = false; + } + } + + private async handleMissingEmail() { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("activeUserEmailNotFoundLoggingYouOut"), + }); + + setTimeout(async () => { + // We can't simply redirect to `/login` because the user is authed and the unauthGuard + // will prevent navigation. We must logout the user first via messagingService, which + // redirects to `/`, which will be handled by the redirectGuard to navigate the user to `/login`. + // The timeout just gives the user a chance to see the error toast before process reload runs on logout. + await this.loginDecryptionOptionsService.logOut(); + }, 5000); + } + + private observeAndPersistRememberDeviceValueChanges() { + this.rememberDeviceControl.valueChanges + .pipe( + takeUntilDestroyed(this.destroyRef), + switchMap((value) => + defer(() => this.deviceTrustService.setShouldTrustDevice(this.activeAccountId, value)), + ), + ) + .subscribe(); + } + + private async setRememberDeviceDefaultValueFromState() { + const rememberDeviceFromState = await this.deviceTrustService.getShouldTrustDevice( + this.activeAccountId, + ); + + const rememberDevice = rememberDeviceFromState ?? true; + + this.rememberDeviceControl.setValue(rememberDevice); + } + + private async loadNewUserData() { + this.state = State.NewUser; + + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "loggedInExclamation", + }, + pageSubtitle: { + key: "rememberThisDeviceToMakeFutureLoginsSeamless", + }, + }); + + const autoEnrollStatus$ = defer(() => + this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(), + ).pipe( + switchMap((organizationIdentifier) => { + if (organizationIdentifier == undefined) { + return throwError(() => new Error(this.i18nService.t("ssoIdentifierRequired"))); + } + + return from(this.organizationApiService.getAutoEnrollStatus(organizationIdentifier)); + }), + catchError((err: unknown) => { + this.validationService.showError(err); + return of(undefined); + }), + ); + + const autoEnrollStatus = await firstValueFrom(autoEnrollStatus$); + + this.newUserOrgId = autoEnrollStatus.id; + } + + private loadExistingUserUntrustedDeviceData(userDecryptionOptions: UserDecryptionOptions) { + this.state = State.ExistingUserUntrustedDevice; + + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "deviceApprovalRequiredV2", + }, + pageSubtitle: { + key: "selectAnApprovalOptionBelow", + }, + }); + + this.canApproveFromOtherDevice = + userDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false; + this.canRequestAdminApproval = + userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false; + this.canApproveWithMasterPassword = userDecryptionOptions?.hasMasterPassword || false; + } + + protected createUser = async () => { + if (this.state !== State.NewUser) { + return; + } + + try { + const { publicKey, privateKey } = await this.keyService.initAccount(); + const keysRequest = new KeysRequest(publicKey, privateKey.encryptedString); + await this.apiService.postAccountKeys(keysRequest); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("accountSuccessfullyCreated"), + }); + + await this.passwordResetEnrollmentService.enroll(this.newUserOrgId); + + if (this.formGroup.value.rememberDevice) { + await this.deviceTrustService.trustDevice(this.activeAccountId); + } + + await this.loginDecryptionOptionsService.handleCreateUserSuccess(); + + if (this.clientType === ClientType.Desktop) { + this.messagingService.send("redrawMenu"); + } + + await this.handleCreateUserSuccessNavigation(); + } catch (err) { + this.validationService.showError(err); + } + }; + + private async handleCreateUserSuccessNavigation() { + if (this.clientType === ClientType.Browser) { + await this.router.navigate(["/tabs/vault"]); + } else { + await this.router.navigate(["/vault"]); + } + } + + protected async approveFromOtherDevice() { + this.loginEmailService.setLoginEmail(this.email); + await this.router.navigate(["/login-with-device"]); + } + + protected async approveWithMasterPassword() { + await this.router.navigate(["/lock"], { + queryParams: { + from: "login-initiated", + }, + }); + } + + protected async requestAdminApproval() { + this.loginEmailService.setLoginEmail(this.email); + await this.router.navigate(["/admin-approval-requested"]); + } + + async logOut() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "logOut" }, + content: { key: "logOutConfirmation" }, + acceptButtonText: { key: "logOut" }, + type: "warning", + }); + + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + if (confirmed) { + this.messagingService.send("logout", { userId: userId }); + } + } +} diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.service.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.service.ts new file mode 100644 index 00000000000..d81d56d6393 --- /dev/null +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.service.ts @@ -0,0 +1,10 @@ +export abstract class LoginDecryptionOptionsService { + /** + * Handles client-specific logic that runs after a user was successfully created + */ + abstract handleCreateUserSuccess(): Promise; + /** + * Logs the user out + */ + abstract logOut(): Promise; +} diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html new file mode 100644 index 00000000000..a1d0f200c15 --- /dev/null +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html @@ -0,0 +1,41 @@ +
+ +

{{ "makeSureYourAccountIsUnlockedAndTheFingerprintEtc" | i18n }}

+ +
{{ "fingerprintPhraseHeader" | i18n }}
+ {{ fingerprintPhrase }} + + + +
+ {{ "needAnotherOptionV1" | i18n }} + {{ + "viewAllLogInOptions" | i18n + }} +
+
+ + +

{{ "youWillBeNotifiedOnceTheRequestIsApproved" | i18n }}

+ +
{{ "fingerprintPhraseHeader" | i18n }}
+ {{ fingerprintPhrase }} + +
+ {{ "troubleLoggingIn" | i18n }} + {{ + "viewAllLogInOptions" | i18n + }} +
+
+
diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts new file mode 100644 index 00000000000..b9a5ee4fe73 --- /dev/null +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -0,0 +1,571 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { IsActiveMatchOptions, Router, RouterModule } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + AuthRequestLoginCredentials, + AuthRequestServiceAbstraction, + LoginEmailServiceAbstraction, + LoginStrategyServiceAbstraction, + LoginSuccessHandlerService, +} from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; +import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; +import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; +import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; + +import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service"; + +enum Flow { + StandardAuthRequest, // when user clicks "Login with device" from /login or "Approve from your other device" from /login-initiated + AdminAuthRequest, // when user clicks "Request admin approval" from /login-initiated +} + +const matchOptions: IsActiveMatchOptions = { + paths: "exact", + queryParams: "ignored", + fragment: "ignored", + matrixParams: "ignored", +}; + +@Component({ + standalone: true, + templateUrl: "./login-via-auth-request.component.html", + imports: [ButtonModule, CommonModule, JslibModule, LinkModule, RouterModule], +}) +export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { + private authRequest: AuthRequest; + private authRequestKeyPair: { publicKey: Uint8Array; privateKey: Uint8Array }; + private authStatus: AuthenticationStatus; + private showResendNotificationTimeoutSeconds = 12; + + protected backToRoute = "/login"; + protected clientType: ClientType; + protected ClientType = ClientType; + protected email: string; + protected fingerprintPhrase: string; + protected showResendNotification = false; + protected Flow = Flow; + protected flow = Flow.StandardAuthRequest; + + constructor( + private accountService: AccountService, + private anonymousHubService: AnonymousHubService, + private appIdService: AppIdService, + private authRequestApiService: AuthRequestApiService, + private authRequestService: AuthRequestServiceAbstraction, + private authService: AuthService, + private cryptoFunctionService: CryptoFunctionService, + private deviceTrustService: DeviceTrustServiceAbstraction, + private i18nService: I18nService, + private logService: LogService, + private loginEmailService: LoginEmailServiceAbstraction, + private loginStrategyService: LoginStrategyServiceAbstraction, + private passwordGenerationService: PasswordGenerationServiceAbstraction, + private platformUtilsService: PlatformUtilsService, + private router: Router, + private toastService: ToastService, + private validationService: ValidationService, + private loginSuccessHandlerService: LoginSuccessHandlerService, + ) { + this.clientType = this.platformUtilsService.getClientType(); + + // Gets SignalR push notification + // Only fires on approval to prevent enumeration + this.authRequestService.authRequestPushNotification$ + .pipe(takeUntilDestroyed()) + .subscribe((requestId) => { + this.verifyAndHandleApprovedAuthReq(requestId).catch((e: Error) => { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("error"), + message: e.message, + }); + + this.logService.error("Failed to use approved auth request: " + e.message); + }); + }); + } + + async ngOnInit(): Promise { + // Get the authStatus early because we use it in both flows + this.authStatus = await firstValueFrom(this.authService.activeAccountStatus$); + + const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; + + if (userHasAuthenticatedViaSSO) { + this.backToRoute = "/login-initiated"; + } + + /** + * The LoginViaAuthRequestComponent handles both the `login-with-device` and + * the `admin-approval-requested` routes. Therefore we check the route to determine + * which flow to initialize. + */ + if (this.router.isActive("admin-approval-requested", matchOptions)) { + await this.initAdminAuthRequestFlow(); + } else { + await this.initStandardAuthRequestFlow(); + } + } + + private async initAdminAuthRequestFlow(): Promise { + this.flow = Flow.AdminAuthRequest; + + // Get email from state for admin auth requests because it is available and also + // prevents it from being lost on refresh as the loginEmailService email does not persist. + this.email = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.email)), + ); + + if (!this.email) { + await this.handleMissingEmail(); + return; + } + + // We only allow a single admin approval request to be active at a time + // so we must check state to see if we have an existing one or not + const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const existingAdminAuthRequest = await this.authRequestService.getAdminAuthRequest(userId); + + if (existingAdminAuthRequest) { + await this.handleExistingAdminAuthRequest(existingAdminAuthRequest, userId); + } else { + await this.startAdminAuthRequestLogin(); + } + } + + private async initStandardAuthRequestFlow(): Promise { + this.flow = Flow.StandardAuthRequest; + + this.email = await firstValueFrom(this.loginEmailService.loginEmail$); + + if (!this.email) { + await this.handleMissingEmail(); + return; + } + + await this.startStandardAuthRequestLogin(); + } + + private async handleMissingEmail(): Promise { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("userEmailMissing"), + }); + + await this.router.navigate([this.backToRoute]); + } + + async ngOnDestroy(): Promise { + await this.anonymousHubService.stopHubConnection(); + } + + private async startAdminAuthRequestLogin(): Promise { + try { + await this.buildAuthRequest(AuthRequestType.AdminApproval); + + const authRequestResponse = await this.authRequestApiService.postAdminAuthRequest( + this.authRequest, + ); + const adminAuthReqStorable = new AdminAuthRequestStorable({ + id: authRequestResponse.id, + privateKey: this.authRequestKeyPair.privateKey, + }); + + const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; + await this.authRequestService.setAdminAuthRequest(adminAuthReqStorable, userId); + + if (authRequestResponse.id) { + await this.anonymousHubService.createHubConnection(authRequestResponse.id); + } + } catch (e) { + this.logService.error(e); + } + } + + protected async startStandardAuthRequestLogin(): Promise { + this.showResendNotification = false; + + try { + await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock); + + const authRequestResponse = await this.authRequestApiService.postAuthRequest( + this.authRequest, + ); + + if (authRequestResponse.id) { + await this.anonymousHubService.createHubConnection(authRequestResponse.id); + } + } catch (e) { + this.logService.error(e); + } + + setTimeout(() => { + this.showResendNotification = true; + }, this.showResendNotificationTimeoutSeconds * 1000); + } + + private async buildAuthRequest(authRequestType: AuthRequestType): Promise { + const authRequestKeyPairArray = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); + + this.authRequestKeyPair = { + publicKey: authRequestKeyPairArray[0], + privateKey: authRequestKeyPairArray[1], + }; + + const deviceIdentifier = await this.appIdService.getAppId(); + const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey); + const accessCode = await this.passwordGenerationService.generatePassword({ + type: "password", + length: 25, + }); + + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( + this.email, + this.authRequestKeyPair.publicKey, + ); + + this.authRequest = new AuthRequest( + this.email, + deviceIdentifier, + publicKey, + authRequestType, + accessCode, + ); + } + + private async handleExistingAdminAuthRequest( + adminAuthRequestStorable: AdminAuthRequestStorable, + userId: UserId, + ): Promise { + // Note: on login, the SSOLoginStrategy will also call to see if an existing admin auth req + // has been approved and handle it if so. + + // Regardless, we always retrieve the auth request from the server and verify and handle status changes here as well + let adminAuthRequestResponse: AuthRequestResponse; + + try { + adminAuthRequestResponse = await this.authRequestApiService.getAuthRequest( + adminAuthRequestStorable.id, + ); + } catch (error) { + if (error instanceof ErrorResponse && error.statusCode === HttpStatusCode.NotFound) { + return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); + } + } + + // Request doesn't exist anymore + if (!adminAuthRequestResponse) { + return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); + } + + // Re-derive the user's fingerprint phrase + // It is important to not use the server's public key here as it could have been compromised via MITM + const derivedPublicKeyArrayBuffer = await this.cryptoFunctionService.rsaExtractPublicKey( + adminAuthRequestStorable.privateKey, + ); + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( + this.email, + derivedPublicKeyArrayBuffer, + ); + + // Request denied + if (adminAuthRequestResponse.isAnswered && !adminAuthRequestResponse.requestApproved) { + return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); + } + + // Request approved + if (adminAuthRequestResponse.requestApproved) { + return await this.decryptViaApprovedAuthRequest( + adminAuthRequestResponse, + adminAuthRequestStorable.privateKey, + userId, + ); + } + + // Request still pending response from admin + // set keypair and create hub connection so that any approvals will be received via push notification + this.authRequestKeyPair = { privateKey: adminAuthRequestStorable.privateKey, publicKey: null }; + await this.anonymousHubService.createHubConnection(adminAuthRequestStorable.id); + } + + private async verifyAndHandleApprovedAuthReq(requestId: string): Promise { + /** + * *********************************** + * Standard Auth Request Flows + * *********************************** + * + * Flow 1: Unauthed user requests approval from device; Approving device has a masterKey in memory. + * + * Unauthed user clicks "Login with device" > navigates to /login-with-device which creates a StandardAuthRequest + * > receives approval from a device with authRequestPublicKey(masterKey) > decrypts masterKey > decrypts userKey > proceed to vault + * + * Flow 2: Unauthed user requests approval from device; Approving device does NOT have a masterKey in memory. + * + * Unauthed user clicks "Login with device" > navigates to /login-with-device which creates a StandardAuthRequest + * > receives approval from a device with authRequestPublicKey(userKey) > decrypts userKey > proceeds to vault + * + * Note: this flow is an uncommon scenario and relates to TDE off-boarding. The following describes how a user could get into this flow: + * 1) An SSO TD user logs into a device via an Admin auth request approval, therefore this device does NOT have a masterKey in memory. + * 2) The org admin... + * (2a) Changes the member decryption options from "Trusted devices" to "Master password" AND + * (2b) Turns off the "Require single sign-on authentication" policy + * 3) On another device, the user clicks "Login with device", which they can do because the org no longer requires SSO. + * 4) The user approves from the device they had previously logged into with SSO TD, which does NOT have a masterKey in memory (see step 1 above). + * + * Flow 3: Authed SSO TD user requests approval from device; Approving device has a masterKey in memory. + * + * SSO TD user authenticates via SSO > navigates to /login-initiated > clicks "Approve from your other device" + * > navigates to /login-with-device which creates a StandardAuthRequest > receives approval from device with authRequestPublicKey(masterKey) + * > decrypts masterKey > decrypts userKey > establishes trust (if required) > proceeds to vault + * + * Flow 4: Authed SSO TD user requests approval from device; Approving device does NOT have a masterKey in memory. + * + * SSO TD user authenticates via SSO > navigates to /login-initiated > clicks "Approve from your other device" + * > navigates to /login-with-device which creates a StandardAuthRequest > receives approval from device with authRequestPublicKey(userKey) + * > decrypts userKey > establishes trust (if required) > proceeds to vault + * + * *********************************** + * Admin Auth Request Flow + * *********************************** + * + * Flow: Authed SSO TD user requests admin approval. + * + * SSO TD user authenticates via SSO > navigates to /login-initiated > clicks "Request admin approval" + * > navigates to /admin-approval-requested which creates an AdminAuthRequest > receives approval from device with authRequestPublicKey(userKey) + * > decrypts userKey > establishes trust (if required) > proceeds to vault + * + * Note: TDE users are required to be enrolled in admin password reset, which gives the admin access to the user's userKey. + * This is how admins are able to send over the authRequestPublicKey(userKey) to the user to allow them to unlock. + * + * + * Summary Table + * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + * | Flow | Auth Status | Clicks Button [active route] | Navigates to | Approving device has masterKey in memory (see note 1) | + * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + * | Standard Flow 1 | unauthed | "Login with device" [/login] | /login-with-device | yes | + * | Standard Flow 2 | unauthed | "Login with device" [/login] | /login-with-device | no | + * | Standard Flow 3 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | yes | + * | Standard Flow 4 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | no | | + * | Admin Flow | authed | "Request admin approval" [/login-initiated] | /admin-approval-requested | NA - admin requests always send encrypted userKey | + * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + * * Note 1: The phrase "in memory" here is important. It is possible for a user to have a master password for their account, but not have a masterKey IN MEMORY for + * a specific device. For example, if a user registers an account with a master password, then joins an SSO TD org, then logs in to a device via SSO and + * admin auth request, they are now logged into that device but that device does not have masterKey IN MEMORY. + */ + + try { + const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; + + if (userHasAuthenticatedViaSSO) { + // Get the auth request from the server + // User is authenticated, therefore the endpoint does not require an access code. + const authRequestResponse = await this.authRequestApiService.getAuthRequest(requestId); + + if (authRequestResponse.requestApproved) { + // Handles Standard Flows 3-4 and Admin Flow + await this.handleAuthenticatedFlows(authRequestResponse); + } + } else { + // Get the auth request from the server + // User is unauthenticated, therefore the endpoint requires an access code for user verification. + const authRequestResponse = await this.authRequestApiService.getAuthResponse( + requestId, + this.authRequest.accessCode, + ); + + if (authRequestResponse.requestApproved) { + // Handles Standard Flows 1-2 + await this.handleUnauthenticatedFlows(authRequestResponse, requestId); + } + } + } catch (error) { + if (error instanceof ErrorResponse) { + await this.router.navigate([this.backToRoute]); + this.validationService.showError(error); + return; + } + + this.logService.error(error); + } + } + + private async handleAuthenticatedFlows(authRequestResponse: AuthRequestResponse) { + const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; + + await this.decryptViaApprovedAuthRequest( + authRequestResponse, + this.authRequestKeyPair.privateKey, + userId, + ); + } + + private async handleUnauthenticatedFlows( + authRequestResponse: AuthRequestResponse, + requestId: string, + ) { + const authRequestLoginCredentials = await this.buildAuthRequestLoginCredentials( + requestId, + authRequestResponse, + ); + + // Note: keys are set by AuthRequestLoginStrategy success handling + const authResult = await this.loginStrategyService.logIn(authRequestLoginCredentials); + + await this.handlePostLoginNavigation(authResult); + } + + private async decryptViaApprovedAuthRequest( + authRequestResponse: AuthRequestResponse, + privateKey: ArrayBuffer, + userId: UserId, + ): Promise { + /** + * See verifyAndHandleApprovedAuthReq() for flow details. + * + * We determine the type of `key` based on the presence or absence of `masterPasswordHash`: + * - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)] + * - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey) + */ + + if (authRequestResponse.masterPasswordHash) { + // ...in Standard Auth Request Flow 3 + await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( + authRequestResponse, + privateKey, + userId, + ); + } else { + // ...in Standard Auth Request Flow 4 or Admin Auth Request Flow + await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( + authRequestResponse, + privateKey, + userId, + ); + } + + // clear the admin auth request from state so it cannot be used again (it's a one time use) + // TODO: this should eventually be enforced via deleting this on the server once it is used + await this.authRequestService.clearAdminAuthRequest(userId); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("loginApproved"), + }); + + // Now that we have a decrypted user key in memory, we can check if we + // need to establish trust on the current device + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); + + await this.handleSuccessfulLoginNavigation(userId); + } + + /** + * Takes an `AuthRequestResponse` and decrypts the `key` to build an `AuthRequestLoginCredentials` + * object for use in the `AuthRequestLoginStrategy`. + * + * The credentials object that gets built is affected by whether the `authRequestResponse.key` + * is an encrypted MasterKey or an encrypted UserKey. + */ + private async buildAuthRequestLoginCredentials( + requestId: string, + authRequestResponse: AuthRequestResponse, + ): Promise { + /** + * See verifyAndHandleApprovedAuthReq() for flow details. + * + * We determine the type of `key` based on the presence or absence of `masterPasswordHash`: + * - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)] + * - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey) + */ + + if (authRequestResponse.masterPasswordHash) { + // ...in Standard Auth Request Flow 1 + const { masterKey, masterKeyHash } = + await this.authRequestService.decryptPubKeyEncryptedMasterKeyAndHash( + authRequestResponse.key, + authRequestResponse.masterPasswordHash, + this.authRequestKeyPair.privateKey, + ); + + return new AuthRequestLoginCredentials( + this.email, + this.authRequest.accessCode, + requestId, + null, // no userKey + masterKey, + masterKeyHash, + ); + } else { + // ...in Standard Auth Request Flow 2 + const userKey = await this.authRequestService.decryptPubKeyEncryptedUserKey( + authRequestResponse.key, + this.authRequestKeyPair.privateKey, + ); + return new AuthRequestLoginCredentials( + this.email, + this.authRequest.accessCode, + requestId, + userKey, + null, // no masterKey + null, // no masterKeyHash + ); + } + } + + private async handleExistingAdminAuthReqDeletedOrDenied(userId: UserId) { + // clear the admin auth request from state + await this.authRequestService.clearAdminAuthRequest(userId); + + // start new auth request + await this.startAdminAuthRequestLogin(); + } + + private async handlePostLoginNavigation(loginResponse: AuthResult) { + if (loginResponse.requiresTwoFactor) { + await this.router.navigate(["2fa"]); + } else if (loginResponse.forcePasswordReset != ForceSetPasswordReason.None) { + await this.router.navigate(["update-temp-password"]); + } else { + await this.handleSuccessfulLoginNavigation(loginResponse.userId); + } + } + + private async handleSuccessfulLoginNavigation(userId: UserId) { + if (this.flow === Flow.StandardAuthRequest) { + // Only need to set remembered email on standard login with auth req flow + await this.loginEmailService.saveEmailSettings(); + } + + await this.loginSuccessHandlerService.run(userId); + await this.router.navigate(["vault"]); + } +} diff --git a/libs/auth/src/angular/login/default-login-component.service.spec.ts b/libs/auth/src/angular/login/default-login-component.service.spec.ts index 9878372801b..05b24da56cc 100644 --- a/libs/auth/src/angular/login/default-login-component.service.spec.ts +++ b/libs/auth/src/angular/login/default-login-component.service.spec.ts @@ -63,12 +63,6 @@ describe("DefaultLoginComponentService", () => { }); }); - describe("isLoginViaAuthRequestSupported", () => { - it("returns false by default", () => { - expect(service.isLoginViaAuthRequestSupported()).toBe(false); - }); - }); - describe("isLoginWithPasskeySupported", () => { it("returns true when clientType is Web", () => { service["clientType"] = ClientType.Web; diff --git a/libs/auth/src/angular/login/default-login-component.service.ts b/libs/auth/src/angular/login/default-login-component.service.ts index a06ad1e6539..84a7d923d12 100644 --- a/libs/auth/src/angular/login/default-login-component.service.ts +++ b/libs/auth/src/angular/login/default-login-component.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { LoginComponentService, PasswordPolicies } from "@bitwarden/auth/angular"; @@ -25,10 +27,6 @@ export class DefaultLoginComponentService implements LoginComponentService { return null; } - isLoginViaAuthRequestSupported(): boolean { - return false; - } - isLoginWithPasskeySupported(): boolean { return this.clientType === ClientType.Web; } diff --git a/libs/auth/src/angular/login/login-component.service.ts b/libs/auth/src/angular/login/login-component.service.ts index 213e09e3520..8ca857cef59 100644 --- a/libs/auth/src/angular/login/login-component.service.ts +++ b/libs/auth/src/angular/login/login-component.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; @@ -23,11 +25,6 @@ export abstract class LoginComponentService { */ getOrgPolicies: () => Promise; - /** - * Indicates whether login with device (auth request) is supported on the given client - */ - isLoginViaAuthRequestSupported: () => boolean; - /** * Indicates whether login with passkey is supported on the given client */ diff --git a/libs/auth/src/angular/login/login.component.html b/libs/auth/src/angular/login/login.component.html index 0afc4659db3..c7837db74f2 100644 --- a/libs/auth/src/angular/login/login.component.html +++ b/libs/auth/src/angular/login/login.component.html @@ -20,8 +20,8 @@ formControlName="email" bitInput appAutofocus - (blur)="onEmailBlur($event)" - (keyup.enter)="continue()" + (input)="onEmailInput($event)" + (keyup.enter)="continuePressed()" /> @@ -33,7 +33,7 @@
- @@ -54,33 +54,10 @@ - - - - {{ "useSingleSignOn" | i18n }} - - - - - +
@@ -97,14 +74,6 @@ {{ "getMasterPasswordHint" | i18n }} - - -
- +
{{ "or" | i18n }}
diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 0193e4c4035..f9aaa5d1e05 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, ElementRef, Input, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom, Subject, take, takeUntil, tap } from "rxjs"; @@ -8,14 +8,13 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginEmailServiceAbstraction, LoginStrategyServiceAbstraction, + LoginSuccessHandlerService, PasswordLoginCredentials, - RegisterRouteService, } from "@bitwarden/auth/common"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { CaptchaIFrame } from "@bitwarden/common/auth/captcha-iframe"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; @@ -24,14 +23,12 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { AsyncActionsModule, @@ -72,19 +69,15 @@ export enum LoginUiState { ], }) export class LoginComponent implements OnInit, OnDestroy { - @ViewChild("masterPasswordInputRef") masterPasswordInputRef: ElementRef; - @Input() captchaSiteKey: string = null; + @ViewChild("masterPasswordInputRef") masterPasswordInputRef: ElementRef | undefined; private destroy$ = new Subject(); - private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions = undefined; + private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions | undefined = undefined; readonly Icons = { WaveIcon, VaultIcon }; - captcha: CaptchaIFrame; - captchaToken: string = null; clientType: ClientType; ClientType = ClientType; LoginUiState = LoginUiState; - registerRoute$ = this.registerRouteService.registerRoute$(); // TODO: remove when email verification flag is removed isKnownDevice = false; loginUiState: LoginUiState = LoginUiState.EMAIL_ENTRY; @@ -100,19 +93,13 @@ export class LoginComponent implements OnInit, OnDestroy { { updateOn: "submit" }, ); - get emailFormControl(): FormControl { + get emailFormControl(): FormControl { return this.formGroup.controls.email; } - /** - * LoginViaAuthRequestSupported is a boolean that determines if we show the Login with device button. - * An AuthRequest is the mechanism that allows users to login to the client via a device that is already logged in. - */ - loginViaAuthRequestSupported = false; - // Web properties - enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; - policies: Policy[]; + enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions | undefined; + policies: Policy[] | undefined; showResetPasswordAutoEnrollWarning = false; // Desktop properties @@ -124,7 +111,6 @@ export class LoginComponent implements OnInit, OnDestroy { private appIdService: AppIdService, private broadcasterService: BroadcasterService, private devicesApiService: DevicesApiServiceAbstraction, - private environmentService: EnvironmentService, private formBuilder: FormBuilder, private i18nService: I18nService, private loginEmailService: LoginEmailServiceAbstraction, @@ -135,19 +121,20 @@ export class LoginComponent implements OnInit, OnDestroy { private passwordStrengthService: PasswordStrengthServiceAbstraction, private platformUtilsService: PlatformUtilsService, private policyService: InternalPolicyService, - private registerRouteService: RegisterRouteService, private router: Router, - private syncService: SyncService, private toastService: ToastService, private logService: LogService, private validationService: ValidationService, private configService: ConfigService, + private loginSuccessHandlerService: LoginSuccessHandlerService, ) { this.clientType = this.platformUtilsService.getClientType(); - this.loginViaAuthRequestSupported = this.loginComponentService.isLoginViaAuthRequestSupported(); } async ngOnInit(): Promise { + // Add popstate listener to listen for browser back button clicks + window.addEventListener("popstate", this.handlePopState); + // TODO: remove this when the UnauthenticatedExtensionUIRefresh feature flag is removed. this.listenForUnauthUiRefreshFlagChanges(); @@ -159,6 +146,9 @@ export class LoginComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + // Remove popstate listener + window.removeEventListener("popstate", this.handlePopState); + if (this.clientType === ClientType.Desktop) { // TODO: refactor to not use deprecated broadcaster service. this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); @@ -200,32 +190,23 @@ export class LoginComponent implements OnInit, OnDestroy { const { email, masterPassword } = this.formGroup.value; - await this.setupCaptcha(); - this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { return; } - const credentials = new PasswordLoginCredentials( - email, - masterPassword, - this.captchaToken, - null, - ); + if (!email || !masterPassword) { + this.logService.error("Email and master password are required"); + return; + } + + const credentials = new PasswordLoginCredentials(email, masterPassword); try { const authResult = await this.loginStrategyService.logIn(credentials); await this.saveEmailSettings(); await this.handleAuthResult(authResult); - - if (this.clientType === ClientType.Desktop) { - if (this.captchaSiteKey) { - const content = document.getElementById("content") as HTMLDivElement; - content.setAttribute("style", "width:335px"); - } - } } catch (error) { this.logService.error(error); this.handleSubmitError(error); @@ -248,11 +229,14 @@ export class LoginComponent implements OnInit, OnDestroy { message: this.i18nService.t("invalidMasterPassword"), }, }); + } else { + // Allow other 400 responses to be handled by toast + this.validationService.showError(error); } break; } default: { - // Allow all other errors to be handled by toast + // Allow all other error codes to be handled by toast this.validationService.showError(error); } } @@ -271,12 +255,6 @@ export class LoginComponent implements OnInit, OnDestroy { * to each if-condition block where necessary to stop code execution. */ private async handleAuthResult(authResult: AuthResult): Promise { - if (this.handleCaptchaRequired(authResult)) { - this.captchaSiteKey = authResult.captchaSiteKey; - this.captcha.init(authResult.captchaSiteKey); - return; - } - if (authResult.requiresEncryptionKeyMigration) { /* Legacy accounts used the master key to encrypt data. Migration is required but only performed on Web. */ @@ -297,7 +275,7 @@ export class LoginComponent implements OnInit, OnDestroy { return; } - await this.syncService.fullSync(true); + await this.loginSuccessHandlerService.run(authResult.userId); if (authResult.forcePasswordReset != ForceSetPasswordReason.None) { this.loginEmailService.clearValues(); @@ -318,7 +296,12 @@ export class LoginComponent implements OnInit, OnDestroy { } protected async launchSsoBrowserWindow(clientId: "browser" | "desktop"): Promise { - await this.loginComponentService.launchSsoBrowserWindow(this.emailFormControl.value, clientId); + const email = this.emailFormControl.value; + if (!email) { + this.logService.error("Email is required for SSO login"); + return; + } + await this.loginComponentService.launchSsoBrowserWindow(email, clientId); } protected async evaluatePassword(): Promise { @@ -354,9 +337,14 @@ export class LoginComponent implements OnInit, OnDestroy { const masterPassword = this.formGroup.controls.masterPassword.value; + // Return false if masterPassword is null/undefined since this is only evaluated after successful login + if (!masterPassword) { + return false; + } + const passwordStrength = this.passwordStrengthService.getPasswordStrength( masterPassword, - this.formGroup.value.email, + this.formGroup.value.email ?? undefined, )?.score; return !this.policyService.evaluateMasterPassword( @@ -366,10 +354,6 @@ export class LoginComponent implements OnInit, OnDestroy { ); } - protected showCaptcha(): boolean { - return !Utils.isNullOrWhitespace(this.captchaSiteKey); - } - protected async startAuthRequestLogin(): Promise { this.formGroup.get("masterPassword")?.clearValidators(); this.formGroup.get("masterPassword")?.updateValueAndValidity(); @@ -384,6 +368,7 @@ export class LoginComponent implements OnInit, OnDestroy { protected async validateEmail(): Promise { this.formGroup.controls.email.markAsTouched(); + this.formGroup.controls.email.updateValueAndValidity({ onlySelf: true, emitEvent: true }); return this.formGroup.controls.email.valid; } @@ -402,10 +387,8 @@ export class LoginComponent implements OnInit, OnDestroy { // Reset master password only when going from validated to not validated so that autofill can work properly this.formGroup.controls.masterPassword.reset(); - if (this.loginViaAuthRequestSupported) { - // Reset known device state when going back to email entry if it is supported - this.isKnownDevice = false; - } + // Reset known device state when going back to email entry if it is supported + this.isKnownDevice = false; } else if (this.loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY) { this.loginComponentService.showBackButton(true); this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ @@ -426,8 +409,10 @@ export class LoginComponent implements OnInit, OnDestroy { }); } - if (this.loginViaAuthRequestSupported) { - await this.getKnownDevice(this.emailFormControl.value); + // Check to see if the device is known so we can show the Login with Device option + const email = this.emailFormControl.value; + if (email) { + await this.getKnownDevice(email); } } } @@ -436,11 +421,10 @@ export class LoginComponent implements OnInit, OnDestroy { * Set the email value from the input field. * @param event The event object from the input field. */ - onEmailBlur(event: Event) { + onEmailInput(event: Event) { const emailInput = event.target as HTMLInputElement; this.formGroup.controls.email.setValue(emailInput.value); - // Call setLoginEmail so that the email is pre-populated when navigating to the "enter password" screen. - this.loginEmailService.setLoginEmail(this.formGroup.value.email); + this.loginEmailService.setLoginEmail(emailInput.value); } isLoginWithPasskeySupported() { @@ -452,28 +436,36 @@ export class LoginComponent implements OnInit, OnDestroy { await this.router.navigateByUrl("/hint"); } - protected async goToRegister(): Promise { - // TODO: remove when email verification flag is removed - const registerRoute = await firstValueFrom(this.registerRoute$); - - if (this.emailFormControl.valid) { - await this.router.navigate([registerRoute], { - queryParams: { email: this.emailFormControl.value }, - }); + protected async saveEmailSettings(): Promise { + const email = this.formGroup.value.email; + if (!email) { + this.logService.error("Email is required to save email settings."); return; } - await this.router.navigate([registerRoute]); + await this.loginEmailService.setLoginEmail(email); + this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail ?? false); + await this.loginEmailService.saveEmailSettings(); } - protected async saveEmailSettings(): Promise { - await this.loginEmailService.setLoginEmail(this.formGroup.value.email); - this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); - await this.loginEmailService.saveEmailSettings(); + /** + * Continue button clicked (or enter key pressed). + * Adds the login url to the browser's history so that the back button can be used to go back to the email entry state. + * Needs to be separate from the continue() function because that can be triggered by the browser's forward button. + */ + protected async continuePressed() { + // Add a new entry to the browser's history so that there is a history entry to go back to + history.pushState({}, "", window.location.href); + await this.continue(); } + /** + * Continue to the master password entry state (only if email is validated) + */ protected async continue(): Promise { - if (await this.validateEmail()) { + const isEmailValid = await this.validateEmail(); + + if (isEmailValid) { await this.toggleLoginUiState(LoginUiState.MASTER_PASSWORD_ENTRY); } } @@ -484,46 +476,21 @@ export class LoginComponent implements OnInit, OnDestroy { * @param email - The user's email */ private async getKnownDevice(email: string): Promise { + if (!email) { + this.isKnownDevice = false; + return; + } + try { const deviceIdentifier = await this.appIdService.getAppId(); this.isKnownDevice = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.isKnownDevice = false; } } - private async setupCaptcha(): Promise { - const env = await firstValueFrom(this.environmentService.environment$); - const webVaultUrl = env.getWebVaultUrl(); - - this.captcha = new CaptchaIFrame( - window, - webVaultUrl, - this.i18nService, - (token: string) => { - this.captchaToken = token; - }, - (error: string) => { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: error, - }); - }, - (info: string) => { - this.toastService.showToast({ - variant: "info", - title: this.i18nService.t("info"), - message: info, - }); - }, - ); - } - - private handleCaptchaRequired(authResult: AuthResult): boolean { - return !Utils.isNullOrWhitespace(authResult.captchaSiteKey); - } - private async loadEmailSettings(): Promise { // Try to load the email from memory first const email = await firstValueFrom(this.loginEmailService.loginEmail$); @@ -559,7 +526,7 @@ export class LoginComponent implements OnInit, OnDestroy { const orgPolicies = await this.loginComponentService.getOrgPolicies(); this.policies = orgPolicies?.policies; - this.showResetPasswordAutoEnrollWarning = orgPolicies?.isPolicyAndAutoEnrollEnabled; + this.showResetPasswordAutoEnrollWarning = orgPolicies?.isPolicyAndAutoEnrollEnabled ?? false; let paramEmailIsSet = false; @@ -580,7 +547,8 @@ export class LoginComponent implements OnInit, OnDestroy { await this.loadEmailSettings(); } - if (this.loginViaAuthRequestSupported) { + // Check to see if the device is known so that we can show the Login with Device option + if (this.emailFormControl.value) { await this.getKnownDevice(this.emailFormControl.value); } @@ -625,4 +593,55 @@ export class LoginComponent implements OnInit, OnDestroy { this.clientType !== ClientType.Browser ); } + + /** + * Handle the back button click to transition back to the email entry state. + */ + protected async backButtonClicked() { + history.back(); + } + + /** + * Handle the popstate event to transition back to the email entry state when the back button is clicked. + * Also handles the case where the user clicks the forward button. + * @param event - The popstate event. + */ + private handlePopState = async (event: PopStateEvent) => { + if (this.loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY) { + // Prevent default navigation when the browser's back button is clicked + event.preventDefault(); + // Transition back to email entry state + void this.toggleLoginUiState(LoginUiState.EMAIL_ENTRY); + } else if (this.loginUiState === LoginUiState.EMAIL_ENTRY) { + // Prevent default navigation when the browser's forward button is clicked + event.preventDefault(); + // Continue to the master password entry state + await this.continue(); + } + }; + + /** + * Handle the SSO button click. + * @param event - The event object. + */ + async handleSsoClick() { + const isEmailValid = await this.validateEmail(); + + if (!isEmailValid) { + return; + } + + await this.saveEmailSettings(); + + if (this.clientType === ClientType.Web) { + await this.router.navigate(["/sso"], { + queryParams: { email: this.formGroup.value.email }, + }); + return; + } + + await this.launchSsoBrowserWindow( + this.clientType === ClientType.Browser ? "browser" : "desktop", + ); + } } diff --git a/libs/auth/src/angular/password-callout/password-callout.component.ts b/libs/auth/src/angular/password-callout/password-callout.component.ts index b56acb37c1d..6968f384f07 100644 --- a/libs/auth/src/angular/password-callout/password-callout.component.ts +++ b/libs/auth/src/angular/password-callout/password-callout.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; diff --git a/libs/auth/src/angular/password-hint/password-hint.component.ts b/libs/auth/src/angular/password-hint/password-hint.component.ts index 1ae1fd337b0..996b4d8d92e 100644 --- a/libs/auth/src/angular/password-hint/password-hint.component.ts +++ b/libs/auth/src/angular/password-hint/password-hint.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; diff --git a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts index e2407a65800..86f08e79748 100644 --- a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts index e034e23de43..14600cebf1d 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts @@ -1,12 +1,11 @@ import { MockProxy, mock } from "jest-mock-extended"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { PasswordInputResult } from "../../input-password/password-input-result"; diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts index f2c4d4c98b6..72e26720134 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 7cfa85ec3d4..31b3f7db92a 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router, RouterModule } from "@angular/router"; diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts index d80aec8351d..ef032b72562 100644 --- a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, RouterModule } from "@angular/router"; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts index f01a8c71bba..4b13c6666e2 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, RouterModule } from "@angular/router"; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index 5ce606b5ec6..e365ff09aa2 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts index fa3ad2ae2b9..6047cc3d27a 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts @@ -28,7 +28,8 @@ import { } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests"; import { LoginEmailService } from "../../../common"; import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts index 781dd0f154c..8d80a38d7ad 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index da49067d7b6..7a2b334eb42 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -9,9 +9,7 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -22,7 +20,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { PasswordInputResult } from "../input-password/password-input-result"; diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index 76477a0e5df..84c580662be 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { @@ -7,10 +9,8 @@ import { import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -19,7 +19,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; 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 { KeyService } from "@bitwarden/key-management"; +import { PBKDF2KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { SetPasswordCredentials, diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts index 80b0adc2bc2..b54529f6a2c 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; @@ -13,6 +15,8 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ToastService } from "../../../../components/src/toast"; import { InputPasswordComponent } from "../input-password/input-password.component"; import { PasswordInputResult } from "../input-password/password-input-result"; diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts index 165b4a61805..18c4df9c18b 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts @@ -1,6 +1,8 @@ -import { PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey } from "@bitwarden/common/types/key"; +import { PBKDF2KdfConfig } from "@bitwarden/key-management"; export interface SetPasswordCredentials { masterKey: MasterKey; diff --git a/libs/auth/src/angular/sso/default-sso-component.service.ts b/libs/auth/src/angular/sso/default-sso-component.service.ts new file mode 100644 index 00000000000..1af7fe3948a --- /dev/null +++ b/libs/auth/src/angular/sso/default-sso-component.service.ts @@ -0,0 +1,3 @@ +import { SsoComponentService } from "./sso-component.service"; + +export class DefaultSsoComponentService implements SsoComponentService {} diff --git a/libs/auth/src/angular/sso/sso-component.service.ts b/libs/auth/src/angular/sso/sso-component.service.ts new file mode 100644 index 00000000000..b5712dfacc9 --- /dev/null +++ b/libs/auth/src/angular/sso/sso-component.service.ts @@ -0,0 +1,20 @@ +import { ClientType } from "@bitwarden/common/enums"; + +export type SsoClientType = ClientType.Web | ClientType.Browser | ClientType.Desktop; + +/** + * Abstract class for SSO component services. + */ +export abstract class SsoComponentService { + /** + * Sets the cookies for the SSO component service. + * Used to pass translation messages to the SSO connector page (apps/web/src/connectors/sso.ts) during the SSO handoff process. + * See implementation in WebSsoComponentService for example usage. + */ + setDocumentCookies?(): void; + + /** + * Closes the window. + */ + closeWindow?(): Promise; +} diff --git a/libs/auth/src/angular/sso/sso.component.html b/libs/auth/src/angular/sso/sso.component.html new file mode 100644 index 00000000000..7a3fa8db973 --- /dev/null +++ b/libs/auth/src/angular/sso/sso.component.html @@ -0,0 +1,18 @@ +
+
+ + {{ "loading" | i18n }} +
+
+ + {{ "ssoIdentifier" | i18n }} + + +
+
+ +
+
+
diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts new file mode 100644 index 00000000000..3bcc56ae4cd --- /dev/null +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -0,0 +1,590 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl, FormGroup, Validators, ReactiveFormsModule } from "@angular/forms"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + LoginStrategyServiceAbstraction, + SsoLoginCredentials, + TrustedDeviceUserDecryptionOption, + UserDecryptionOptions, + UserDecryptionOptionsServiceAbstraction, + LoginSuccessHandlerService, +} from "@bitwarden/auth/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; +import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; +import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response"; +import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + AsyncActionsModule, + ButtonModule, + CheckboxModule, + FormFieldModule, + IconButtonModule, + LinkModule, + ToastService, +} from "@bitwarden/components"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; + +import { SsoClientType, SsoComponentService } from "./sso-component.service"; + +interface QueryParams { + code?: string; + state?: string; + redirectUri?: string; + clientId?: string; + codeChallenge?: string; + identifier?: string; + email?: string; +} + +/** + * This component handles the SSO flow. + */ +@Component({ + standalone: true, + templateUrl: "sso.component.html", + imports: [ + AsyncActionsModule, + ButtonModule, + CheckboxModule, + CommonModule, + FormFieldModule, + IconButtonModule, + LinkModule, + JslibModule, + ReactiveFormsModule, + RouterModule, + ], +}) +export class SsoComponent implements OnInit { + protected formGroup = new FormGroup({ + identifier: new FormControl(null, [Validators.required]), + }); + + protected redirectUri: string | undefined; + protected loggingIn = false; + protected identifier: string | undefined; + protected state: string | undefined; + protected codeChallenge: string | undefined; + protected clientId: SsoClientType | undefined; + + formPromise: Promise | undefined; + initiateSsoFormPromise: Promise | undefined; + + get identifierFormControl() { + return this.formGroup.controls.identifier; + } + + constructor( + private ssoLoginService: SsoLoginServiceAbstraction, + private loginStrategyService: LoginStrategyServiceAbstraction, + private router: Router, + private i18nService: I18nService, + private route: ActivatedRoute, + private orgDomainApiService: OrgDomainApiServiceAbstraction, + private validationService: ValidationService, + private configService: ConfigService, + private platformUtilsService: PlatformUtilsService, + private apiService: ApiService, + private cryptoFunctionService: CryptoFunctionService, + private environmentService: EnvironmentService, + private passwordGenerationService: PasswordGenerationServiceAbstraction, + private logService: LogService, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private masterPasswordService: InternalMasterPasswordServiceAbstraction, + private accountService: AccountService, + private toastService: ToastService, + private ssoComponentService: SsoComponentService, + private loginSuccessHandlerService: LoginSuccessHandlerService, + ) { + environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { + this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html"; + }); + + const clientType = this.platformUtilsService.getClientType(); + if (this.isValidSsoClientType(clientType)) { + this.clientId = clientType as SsoClientType; + } + } + + async ngOnInit() { + const qParams: QueryParams = await firstValueFrom(this.route.queryParams); + + // This if statement will pass on the second portion of the SSO flow + // where the user has already authenticated with the identity provider + if (this.hasCodeOrStateParams(qParams)) { + await this.handleCodeAndStateParams(qParams); + return; + } + + // This if statement will pass on the first portion of the SSO flow + if (this.hasRequiredSsoParams(qParams)) { + this.setRequiredSsoVariables(qParams); + return; + } + + if (qParams.identifier != null) { + // SSO Org Identifier in query params takes precedence over claimed domains + this.identifierFormControl.setValue(qParams.identifier); + this.loggingIn = true; + await this.submit(); + return; + } + + await this.initializeIdentifierFromEmailOrStorage(qParams); + } + + /** + * Sets the required SSO variables from the query params + * @param qParams - The query params + */ + private setRequiredSsoVariables(qParams: QueryParams): void { + this.redirectUri = qParams.redirectUri ?? ""; + this.state = qParams.state ?? ""; + this.codeChallenge = qParams.codeChallenge ?? ""; + const clientId = qParams.clientId ?? ""; + if (this.isValidSsoClientType(clientId)) { + this.clientId = clientId; + } else { + throw new Error(`Invalid SSO client type: ${qParams.clientId}`); + } + } + + /** + * Checks if the value is a valid SSO client type + * @param value - The value to check + * @returns True if the value is a valid SSO client type, otherwise false + */ + private isValidSsoClientType(value: string): value is SsoClientType { + return [ClientType.Web, ClientType.Browser, ClientType.Desktop].includes(value as ClientType); + } + + /** + * Checks if the query params have the required SSO params + * @param qParams - The query params + * @returns True if the query params have the required SSO params, false otherwise + */ + private hasRequiredSsoParams(qParams: QueryParams): boolean { + return ( + qParams.clientId != null && + qParams.redirectUri != null && + qParams.state != null && + qParams.codeChallenge != null + ); + } + + /** + * Handles the code and state params + * @param qParams - The query params + */ + private async handleCodeAndStateParams(qParams: QueryParams): Promise { + const codeVerifier = await this.ssoLoginService.getCodeVerifier(); + const state = await this.ssoLoginService.getSsoState(); + await this.ssoLoginService.setCodeVerifier(""); + await this.ssoLoginService.setSsoState(""); + + if (qParams.redirectUri != null) { + this.redirectUri = qParams.redirectUri; + } + + if ( + qParams.code != null && + codeVerifier != null && + state != null && + this.checkState(state, qParams.state ?? "") + ) { + const ssoOrganizationIdentifier = this.getOrgIdentifierFromState(qParams.state ?? ""); + await this.logIn(qParams.code, codeVerifier, ssoOrganizationIdentifier); + } + } + + /** + * Checks if the query params have a code or state + * @param qParams - The query params + * @returns True if the query params have a code or state, false otherwise + */ + private hasCodeOrStateParams(qParams: QueryParams): boolean { + return qParams.code != null && qParams.state != null; + } + + private handleGetClaimedDomainByEmailError(error: unknown): void { + if (error instanceof ErrorResponse) { + const errorResponse: ErrorResponse = error as ErrorResponse; + switch (errorResponse.statusCode) { + case HttpStatusCode.NotFound: + //this is a valid case for a domain not found + return; + + default: + this.validationService.showError(errorResponse); + break; + } + } + } + + submit = async (): Promise => { + if (this.formGroup.invalid) { + return; + } + + const autoSubmit = (await firstValueFrom(this.route.queryParams)).identifier != null; + + this.identifier = this.identifierFormControl.value ?? ""; + await this.ssoLoginService.setOrganizationSsoIdentifier(this.identifier); + this.ssoComponentService.setDocumentCookies?.(); + try { + await this.submitSso(); + } catch (error) { + if (autoSubmit) { + await this.router.navigate(["/login"]); + } else { + this.validationService.showError(error); + } + } + }; + + private async submitSso(returnUri?: string, includeUserIdentifier?: boolean) { + if (this.identifier == null || this.identifier === "") { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("ssoValidationFailed"), + message: this.i18nService.t("ssoIdentifierRequired"), + }); + return; + } + + if (this.clientId == null) { + throw new Error("Client ID is required"); + } + + this.initiateSsoFormPromise = this.apiService.preValidateSso(this.identifier); + const response = await this.initiateSsoFormPromise; + + const authorizeUrl = await this.buildAuthorizeUrl( + returnUri, + includeUserIdentifier, + response.token, + ); + this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true }); + } + + private async buildAuthorizeUrl( + returnUri?: string, + includeUserIdentifier?: boolean, + token?: string, + ): Promise { + let codeChallenge = this.codeChallenge; + let state = this.state; + + const passwordOptions = { + type: "password" as const, + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + + if (codeChallenge == null) { + const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); + codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + await this.ssoLoginService.setCodeVerifier(codeVerifier); + } + + if (state == null) { + state = await this.passwordGenerationService.generatePassword(passwordOptions); + if (returnUri) { + state += `_returnUri='${returnUri}'`; + } + } + + // Add Organization Identifier to state + state += `_identifier=${this.identifier}`; + + // Save state (regardless of new or existing) + await this.ssoLoginService.setSsoState(state); + + const env = await firstValueFrom(this.environmentService.environment$); + + let authorizeUrl = + env.getIdentityUrl() + + "/connect/authorize?" + + "client_id=" + + this.clientId + + "&redirect_uri=" + + encodeURIComponent(this.redirectUri ?? "") + + "&" + + "response_type=code&scope=api offline_access&" + + "state=" + + state + + "&code_challenge=" + + codeChallenge + + "&" + + "code_challenge_method=S256&response_mode=query&" + + "domain_hint=" + + encodeURIComponent(this.identifier ?? "") + + "&ssoToken=" + + encodeURIComponent(token ?? ""); + + if (includeUserIdentifier) { + const userIdentifier = await this.apiService.getSsoUserIdentifier(); + authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`; + } + + return authorizeUrl; + } + + private async logIn(code: string, codeVerifier: string, orgSsoIdentifier: string): Promise { + this.loggingIn = true; + try { + const email = await this.ssoLoginService.getSsoEmail(); + const redirectUri = this.redirectUri ?? ""; + const credentials = new SsoLoginCredentials( + code, + codeVerifier, + redirectUri, + orgSsoIdentifier, + email, + ); + this.formPromise = this.loginStrategyService.logIn(credentials); + const authResult = await this.formPromise; + + if (authResult.requiresTwoFactor) { + return await this.handleTwoFactorRequired(orgSsoIdentifier); + } + + // Everything after the 2FA check is considered a successful login + // Just have to figure out where to send the user + await this.loginSuccessHandlerService.run(authResult.userId); + + // Save off the OrgSsoIdentifier for use in the TDE flows (or elsewhere) + // - TDE login decryption options component + // - Browser SSO on extension open + // Note: you cannot set this in state before 2FA b/c there won't be an account in state. + await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier); + + // Users enrolled in admin acct recovery can be forced to set a new password after + // having the admin set a temp password for them (affects TDE & standard users) + if (authResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) { + // Weak password is not a valid scenario here b/c we cannot have evaluated a MP yet + return await this.handleForcePasswordReset(orgSsoIdentifier); + } + + // must come after 2fa check since user decryption options aren't available if 2fa is required + const userDecryptionOpts = await firstValueFrom( + this.userDecryptionOptionsService.userDecryptionOptions$, + ); + + const tdeEnabled = userDecryptionOpts.trustedDeviceOption + ? await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption) + : false; + + if (tdeEnabled) { + return await this.handleTrustedDeviceEncryptionEnabled(userDecryptionOpts); + } + + // In the standard, non TDE case, a user must set password if they don't + // have one and they aren't using key connector. + // Note: TDE & Key connector are mutually exclusive org config options. + const requireSetPassword = + !userDecryptionOpts.hasMasterPassword && + userDecryptionOpts.keyConnectorOption === undefined; + + if (requireSetPassword || authResult.resetMasterPassword) { + // Change implies going no password -> password in this case + return await this.handleChangePasswordRequired(orgSsoIdentifier); + } + + // Standard SSO login success case + return await this.handleSuccessfulLogin(); + } catch (e) { + await this.handleLoginError(e); + } + } + + private async isTrustedDeviceEncEnabled( + trustedDeviceOption: TrustedDeviceUserDecryptionOption, + ): Promise { + return trustedDeviceOption !== undefined; + } + + private async handleTwoFactorRequired(orgIdentifier: string) { + await this.router.navigate(["2fa"], { + queryParams: { + identifier: orgIdentifier, + sso: "true", + }, + }); + } + + private async handleTrustedDeviceEncryptionEnabled( + userDecryptionOpts: UserDecryptionOptions, + ): Promise { + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + + if (!userId) { + return; + } + + // Tde offboarding takes precedence + if ( + !userDecryptionOpts.hasMasterPassword && + userDecryptionOpts.trustedDeviceOption?.isTdeOffboarding + ) { + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.TdeOffboarding, + userId, + ); + } else if ( + // If user doesn't have a MP, but has reset password permission, they must set a MP + !userDecryptionOpts.hasMasterPassword && + userDecryptionOpts.trustedDeviceOption?.hasManageResetPasswordPermission + ) { + // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device) + // Note: we cannot directly navigate in this scenario as we are in a pre-decryption state, and + // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key. + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, + userId, + ); + } + + if (this.ssoComponentService?.closeWindow) { + await this.ssoComponentService.closeWindow(); + } else { + await this.router.navigate(["login-initiated"]); + } + } + + private async handleChangePasswordRequired(orgIdentifier: string) { + const emailVerification = await this.configService.getFeatureFlag( + FeatureFlag.EmailVerification, + ); + + let route = "set-password"; + if (emailVerification) { + route = "set-password-jit"; + } + + await this.router.navigate([route], { + queryParams: { + identifier: orgIdentifier, + }, + }); + } + + private async handleForcePasswordReset(orgIdentifier: string) { + await this.router.navigate(["update-temp-password"], { + queryParams: { + identifier: orgIdentifier, + }, + }); + } + + private async handleSuccessfulLogin() { + await this.router.navigate(["lock"]); + } + + private async handleLoginError(e: unknown) { + this.logService.error(e); + + // TODO: Key Connector Service should pass this error message to the logout callback instead of displaying here + if (e instanceof Error && e.message === "Key Connector error") { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("ssoKeyConnectorError"), + }); + } + } + + private getOrgIdentifierFromState(state: string): string { + if (state === null || state === undefined) { + return ""; + } + + const stateSplit = state.split("_identifier="); + return stateSplit.length > 1 ? stateSplit[1] : ""; + } + + private checkState(state: string, checkState: string): boolean { + if (state === null || state === undefined) { + return false; + } + if (checkState === null || checkState === undefined) { + return false; + } + + const stateSplit = state.split("_identifier="); + const checkStateSplit = checkState.split("_identifier="); + return stateSplit[0] === checkStateSplit[0]; + } + + /** + * Attempts to initialize the SSO identifier from email or storage. + * Note: this flow is written for web but both browser and desktop + * redirect here on SSO button click. + * @param qParams - The query params + */ + private async initializeIdentifierFromEmailOrStorage(qParams: QueryParams): Promise { + // Check if email matches any claimed domains + if (qParams.email) { + // show loading spinner + this.loggingIn = true; + try { + if (await this.configService.getFeatureFlag(FeatureFlag.VerifiedSsoDomainEndpoint)) { + const response: ListResponse = + await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(qParams.email); + + if (response.data.length > 0) { + this.identifierFormControl.setValue(response.data[0].organizationIdentifier); + await this.submit(); + return; + } + } else { + const response: OrganizationDomainSsoDetailsResponse = + await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email); + + if (response?.ssoAvailable && response?.verifiedDate) { + this.identifierFormControl.setValue(response.organizationIdentifier); + await this.submit(); + return; + } + } + } catch (error) { + this.handleGetClaimedDomainByEmailError(error); + } + + this.loggingIn = false; + } + + // Fallback to state svc if domain is unclaimed + const storedIdentifier = await this.ssoLoginService.getOrganizationSsoIdentifier(); + if (storedIdentifier != null) { + this.identifierFormControl.setValue(storedIdentifier); + } + } +} diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts index 0c3e6ab71e8..20fc238efa3 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; diff --git a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts index 8c62136d63c..40c3106b188 100644 --- a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { animate, style, transition, trigger } from "@angular/animations"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts index d42e7d7d15b..f7199d06499 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; import { diff --git a/libs/auth/src/common/abstractions/auth-request-api.service.ts b/libs/auth/src/common/abstractions/auth-request-api.service.ts new file mode 100644 index 00000000000..1b0befc0df4 --- /dev/null +++ b/libs/auth/src/common/abstractions/auth-request-api.service.ts @@ -0,0 +1,37 @@ +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; + +export abstract class AuthRequestApiService { + /** + * Gets an auth request by its ID. + * + * @param requestId The ID of the auth request. + * @returns A promise that resolves to the auth request response. + */ + abstract getAuthRequest: (requestId: string) => Promise; + + /** + * Gets an auth request response by its ID and access code. + * + * @param requestId The ID of the auth request. + * @param accessCode The access code of the auth request. + * @returns A promise that resolves to the auth request response. + */ + abstract getAuthResponse: (requestId: string, accessCode: string) => Promise; + + /** + * Sends an admin auth request. + * + * @param request The auth request object. + * @returns A promise that resolves to the auth request response. + */ + abstract postAdminAuthRequest: (request: AuthRequest) => Promise; + + /** + * Sends an auth request. + * + * @param request The auth request object. + * @returns A promise that resolves to the auth request response. + */ + abstract postAuthRequest: (request: AuthRequest) => Promise; +} diff --git a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts index 371c32e42d3..1829fe6b0c9 100644 --- a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; diff --git a/libs/auth/src/common/abstractions/index.ts b/libs/auth/src/common/abstractions/index.ts index e686de52013..c0dc500ddb9 100644 --- a/libs/auth/src/common/abstractions/index.ts +++ b/libs/auth/src/common/abstractions/index.ts @@ -1,5 +1,8 @@ +export * from "./auth-request-api.service"; export * from "./pin.service.abstraction"; export * from "./login-email.service"; export * from "./login-strategy.service"; export * from "./user-decryption-options.service.abstraction"; export * from "./auth-request.service.abstraction"; +export * from "./login-approval-component.service.abstraction"; +export * from "./login-success-handler.service"; diff --git a/libs/auth/src/common/abstractions/login-approval-component.service.abstraction.ts b/libs/auth/src/common/abstractions/login-approval-component.service.abstraction.ts new file mode 100644 index 00000000000..eaa62359808 --- /dev/null +++ b/libs/auth/src/common/abstractions/login-approval-component.service.abstraction.ts @@ -0,0 +1,9 @@ +/** + * Abstraction for the LoginApprovalComponent service. + */ +export abstract class LoginApprovalComponentServiceAbstraction { + /** + * Shows a login requested alert if the window is not visible. + */ + abstract showLoginRequestedAlertIfWindowNotVisible: (email?: string) => Promise; +} diff --git a/libs/auth/src/common/abstractions/login-email.service.ts b/libs/auth/src/common/abstractions/login-email.service.ts index 496d890f162..fc72c4cd262 100644 --- a/libs/auth/src/common/abstractions/login-email.service.ts +++ b/libs/auth/src/common/abstractions/login-email.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; export abstract class LoginEmailServiceAbstraction { diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index a46636532bf..1088d6de736 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; @@ -71,4 +73,8 @@ export abstract class LoginStrategyServiceAbstraction { * Creates a master key from the provided master password and email. */ makePreloginKey: (masterPassword: string, email: string) => Promise; + /** + * Emits true if the two factor session has expired. + */ + twoFactorTimeout$: Observable; } diff --git a/libs/auth/src/common/abstractions/login-success-handler.service.ts b/libs/auth/src/common/abstractions/login-success-handler.service.ts new file mode 100644 index 00000000000..8dee1dd32b9 --- /dev/null +++ b/libs/auth/src/common/abstractions/login-success-handler.service.ts @@ -0,0 +1,10 @@ +import { UserId } from "@bitwarden/common/types/guid"; + +export abstract class LoginSuccessHandlerService { + /** + * Runs any service calls required after a successful login. + * Service calls that should be included in this method are only those required to be awaited after successful login. + * @param userId The user id. + */ + abstract run(userId: UserId): Promise; +} diff --git a/libs/auth/src/common/abstractions/pin.service.abstraction.ts b/libs/auth/src/common/abstractions/pin.service.abstraction.ts index 00ccf934f61..eb1cfaf0ec3 100644 --- a/libs/auth/src/common/abstractions/pin.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/pin.service.abstraction.ts @@ -1,7 +1,7 @@ -import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; import { UserId } from "@bitwarden/common/types/guid"; import { PinKey, UserKey } from "@bitwarden/common/types/key"; +import { KdfConfig } from "@bitwarden/key-management"; import { PinLockType } from "../services"; diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index c0e7d2c00ae..cec4481cd8d 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -3,7 +3,6 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; @@ -23,7 +22,7 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; import { AuthRequestLoginCredentials } from "../models/domain/login-credentials"; diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts index a3e2fda2f28..e546f89032b 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 49140cc2cc0..50443bab0ea 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -4,7 +4,6 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -38,7 +37,7 @@ import { import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey, MasterKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { LoginStrategyServiceAbstraction } from "../abstractions"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 67a286d8195..25f99f47840 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -1,16 +1,16 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BehaviorSubject, filter, firstValueFrom, timeout } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { Argon2KdfConfig, PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { DeviceRequest } from "@bitwarden/common/auth/models/request/identity-token/device.request"; import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/sso-token.request"; @@ -30,10 +30,15 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" 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 { KdfType } from "@bitwarden/common/platform/enums"; import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService } from "@bitwarden/key-management"; +import { + KeyService, + Argon2KdfConfig, + PBKDF2KdfConfig, + KdfConfigService, + KdfType, +} from "@bitwarden/key-management"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; import { diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 4da6272ccab..4ee4fcaeb38 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -3,7 +3,6 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -32,7 +31,7 @@ import { import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { LoginStrategyServiceAbstraction } from "../abstractions"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index 05faef1ba14..c496b7c9674 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BehaviorSubject, firstValueFrom, map, Observable } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 7b5ad4a31b6..ec3ec43134f 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -3,7 +3,6 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -29,7 +28,7 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { AuthRequestServiceAbstraction, diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index b1dffea9b50..e9d1e0a6c88 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts index 07d06a7567d..2bb41faa0e1 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts @@ -2,7 +2,6 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -26,7 +25,7 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey, MasterKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; import { UserApiLoginCredentials } from "../models/domain/login-credentials"; diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.ts index 1097e8e04c2..ef13e631300 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index 88392b57c53..9dacce2cf00 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -2,7 +2,6 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; @@ -24,7 +23,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-ti import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { PrfKey, UserKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; import { WebAuthnLoginCredentials } from "../models/domain/login-credentials"; diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts index df671080986..97696a26699 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/auth/src/common/models/domain/login-credentials.ts b/libs/auth/src/common/models/domain/login-credentials.ts index bfe01aea20f..72cc7413bec 100644 --- a/libs/auth/src/common/models/domain/login-credentials.ts +++ b/libs/auth/src/common/models/domain/login-credentials.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; @@ -12,6 +14,7 @@ export class PasswordLoginCredentials { constructor( public email: string, public masterPassword: string, + // TODO: PM-15162 - captcha is deprecated as part of UI refresh work public captchaToken?: string, public twoFactor?: TokenTwoFactorRequest, ) {} diff --git a/libs/auth/src/common/models/domain/rotateable-key-set.ts b/libs/auth/src/common/models/domain/rotateable-key-set.ts index 5e0faea3396..c6e75f2b60e 100644 --- a/libs/auth/src/common/models/domain/rotateable-key-set.ts +++ b/libs/auth/src/common/models/domain/rotateable-key-set.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PrfKey } from "@bitwarden/common/types/key"; diff --git a/libs/auth/src/common/models/domain/user-decryption-options.ts b/libs/auth/src/common/models/domain/user-decryption-options.ts index a4e61f5dac6..00b78064d83 100644 --- a/libs/auth/src/common/models/domain/user-decryption-options.ts +++ b/libs/auth/src/common/models/domain/user-decryption-options.ts @@ -1,7 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { KeyConnectorUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/key-connector-user-decryption-option.response"; import { TrustedDeviceUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IdentityTokenResponse } from "@bitwarden/common/src/auth/models/response/identity-token.response"; /** diff --git a/libs/auth/src/common/services/auth-request/auth-request-api.service.ts b/libs/auth/src/common/services/auth-request/auth-request-api.service.ts new file mode 100644 index 00000000000..180e0079396 --- /dev/null +++ b/libs/auth/src/common/services/auth-request/auth-request-api.service.ts @@ -0,0 +1,65 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +import { AuthRequestApiService } from "../../abstractions/auth-request-api.service"; + +export class DefaultAuthRequestApiService implements AuthRequestApiService { + constructor( + private apiService: ApiService, + private logService: LogService, + ) {} + + async getAuthRequest(requestId: string): Promise { + try { + const path = `/auth-requests/${requestId}`; + const response = await this.apiService.send("GET", path, null, true, true); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + + async getAuthResponse(requestId: string, accessCode: string): Promise { + try { + const path = `/auth-requests/${requestId}/response?code=${accessCode}`; + const response = await this.apiService.send("GET", path, null, false, true); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + + async postAdminAuthRequest(request: AuthRequest): Promise { + try { + const response = await this.apiService.send( + "POST", + "/auth-requests/admin-request", + request, + true, + true, + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + + async postAuthRequest(request: AuthRequest): Promise { + try { + const response = await this.apiService.send("POST", "/auth-requests/", request, false, true); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } +} diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.ts b/libs/auth/src/common/services/auth-request/auth-request.service.ts index b6a7bfb26b9..4bc0397b43a 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, Subject, firstValueFrom } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/auth/src/common/services/index.ts b/libs/auth/src/common/services/index.ts index 3a8df057796..d1cedebcf36 100644 --- a/libs/auth/src/common/services/index.ts +++ b/libs/auth/src/common/services/index.ts @@ -3,5 +3,7 @@ export * from "./login-email/login-email.service"; export * from "./login-strategies/login-strategy.service"; export * from "./user-decryption-options/user-decryption-options.service"; export * from "./auth-request/auth-request.service"; +export * from "./auth-request/auth-request-api.service"; export * from "./register-route.service"; export * from "./accounts/lock.service"; +export * from "./login-success-handler/default-login-success-handler.service"; diff --git a/libs/auth/src/common/services/login-email/login-email.service.ts b/libs/auth/src/common/services/login-email/login-email.service.ts index bb89b412c51..aa13afd5004 100644 --- a/libs/auth/src/common/services/login-email/login-email.service.ts +++ b/libs/auth/src/common/services/login-email/login-email.service.ts @@ -1,9 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, firstValueFrom, switchMap } from "rxjs"; 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"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { GlobalState, KeyDefinition, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index b0d9228f446..5fcbefbef2f 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -5,13 +5,11 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; @@ -27,7 +25,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" 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 { KdfType } from "@bitwarden/common/platform/enums"; import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { FakeAccountService, @@ -37,7 +34,7 @@ import { } from "@bitwarden/common/spec"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KdfType, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { AuthRequestServiceAbstraction, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 721ee984974..57a653b205e 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatestWith, distinctUntilChanged, @@ -6,24 +8,19 @@ import { Observable, shareReplay, Subscription, + BehaviorSubject, } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { - Argon2KdfConfig, - KdfConfig, - PBKDF2KdfConfig, -} from "@bitwarden/common/auth/models/domain/kdf-config"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request"; @@ -36,13 +33,21 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" 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 { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { MasterKey } from "@bitwarden/common/types/key"; -import { KeyService } from "@bitwarden/key-management"; +import { + KdfType, + KeyService, + Argon2KdfConfig, + KdfConfig, + PBKDF2KdfConfig, + KdfConfigService, +} from "@bitwarden/key-management"; import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction } from "../../abstractions"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; @@ -68,7 +73,7 @@ import { CACHE_KEY, } from "./login-strategy.state"; -const sessionTimeoutLength = 2 * 60 * 1000; // 2 minutes +const sessionTimeoutLength = 5 * 60 * 1000; // 5 minutes export class LoginStrategyService implements LoginStrategyServiceAbstraction { private sessionTimeoutSubscription: Subscription; @@ -76,6 +81,9 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { private loginStrategyCacheState: GlobalState; private loginStrategyCacheExpirationState: GlobalState; private authRequestPushNotificationState: GlobalState; + private twoFactorTimeoutSubject = new BehaviorSubject(false); + + twoFactorTimeout$: Observable = this.twoFactorTimeoutSubject.asObservable(); private loginStrategy$: Observable< | UserApiLoginStrategy @@ -123,7 +131,14 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { ); this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, - () => this.clearCache(), + async () => { + this.twoFactorTimeoutSubject.next(true); + try { + await this.clearCache(); + } catch (e) { + this.logService.error("Failed to clear cache during session timeout", e); + } + }, ); this.currentAuthType$ = this.currentAuthnTypeState.state$; @@ -189,6 +204,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { | WebAuthnLoginCredentials, ): Promise { await this.clearCache(); + this.twoFactorTimeoutSubject.next(false); await this.currentAuthnTypeState.update((_) => credentials.type); @@ -273,6 +289,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { private async clearCache(): Promise { await this.currentAuthnTypeState.update((_) => null); await this.loginStrategyCacheState.update((_) => null); + this.twoFactorTimeoutSubject.next(false); await this.clearSessionTimeout(); } diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.state.ts b/libs/auth/src/common/services/login-strategies/login-strategy.state.ts index 5dc03755050..1592c51453c 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.state.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { KeyDefinition, LOGIN_STRATEGY_MEMORY } from "@bitwarden/common/platform/state"; diff --git a/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts b/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts new file mode 100644 index 00000000000..215329051df --- /dev/null +++ b/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts @@ -0,0 +1,16 @@ +import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; + +import { LoginSuccessHandlerService } from "../../abstractions/login-success-handler.service"; + +export class DefaultLoginSuccessHandlerService implements LoginSuccessHandlerService { + constructor( + private syncService: SyncService, + private userAsymmetricKeysRegenerationService: UserAsymmetricKeysRegenerationService, + ) {} + async run(userId: UserId): Promise { + await this.syncService.fullSync(true); + await this.userAsymmetricKeysRegenerationService.regenerateIfNeeded(userId); + } +} diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts index 2a01802fa57..01fc77e4a03 100644 --- a/libs/auth/src/common/services/pin/pin.service.implementation.ts +++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts @@ -1,9 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; @@ -19,6 +19,7 @@ import { } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key"; +import { KdfConfig, KdfConfigService } from "@bitwarden/key-management"; import { PinServiceAbstraction } from "../../abstractions/pin.service.abstraction"; diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index 6befec06994..d254be4e875 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -1,7 +1,5 @@ import { mock } from "jest-mock-extended"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; -import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -18,6 +16,7 @@ import { } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key"; +import { DEFAULT_KDF_CONFIG, KdfConfigService } from "@bitwarden/key-management"; import { PinService, diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts index e4200c759e8..7c44a6f1682 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, map } from "rxjs"; import { @@ -6,6 +8,8 @@ import { USER_DECRYPTION_OPTIONS_DISK, UserKeyDefinition, } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; diff --git a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts index 717e80b110d..24b3adacc21 100644 --- a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts +++ b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts @@ -18,6 +18,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { try { // Attempt to decode from URL-safe Base64 to UTF-8 decodedPayloadJSON = Utils.fromUrlB64ToUtf8(encodedPayload); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (decodingError) { throw new Error("Cannot decode the token"); } @@ -26,6 +28,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { // Attempt to parse the JSON payload const decodedToken = JSON.parse(decodedPayloadJSON); return decodedToken; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (jsonError) { throw new Error("Cannot parse the token's payload into JSON"); } diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 6004a56fb55..9be942d38de 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -1,5 +1,22 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "resolveJsonModule": true, + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/billing/jest.config.js b/libs/billing/jest.config.js index d9bae9633ea..c43606191b9 100644 --- a/libs/billing/jest.config.js +++ b/libs/billing/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/billing/tsconfig.json b/libs/billing/tsconfig.json index 6004a56fb55..bb08eb89d1c 100644 --- a/libs/billing/tsconfig.json +++ b/libs/billing/tsconfig.json @@ -1,5 +1,6 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": {}, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/common/jest.config.js b/libs/common/jest.config.js index d7f78abbf38..7e6c0997b9c 100644 --- a/libs/common/jest.config.js +++ b/libs/common/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/common/spec/fake-account-service.ts b/libs/common/spec/fake-account-service.ts index db84143d3a6..05e44d5db18 100644 --- a/libs/common/spec/fake-account-service.ts +++ b/libs/common/spec/fake-account-service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { ReplaySubject, combineLatest, map } from "rxjs"; diff --git a/libs/common/spec/fake-state-provider.ts b/libs/common/spec/fake-state-provider.ts index 666487ecf09..b5105bb24ba 100644 --- a/libs/common/spec/fake-state-provider.ts +++ b/libs/common/spec/fake-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { Observable, map, of, switchMap, take } from "rxjs"; diff --git a/libs/common/spec/fake-state.ts b/libs/common/spec/fake-state.ts index 2400e470d42..e4b42e357b6 100644 --- a/libs/common/spec/fake-state.ts +++ b/libs/common/spec/fake-state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, ReplaySubject, concatMap, filter, firstValueFrom, map, timeout } from "rxjs"; import { diff --git a/libs/common/spec/observable-tracker.ts b/libs/common/spec/observable-tracker.ts index dfb49835933..0ec4aa812f8 100644 --- a/libs/common/spec/observable-tracker.ts +++ b/libs/common/spec/observable-tracker.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, Observable, Subject, Subscription, throwError, timeout } from "rxjs"; /** Test class to enable async awaiting of observable emissions */ diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index 1cead2aa624..51db65d0ce0 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; import { Observable } from "rxjs"; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 9834116c9fc..997974e0581 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionRequest, CollectionAccessDetailsResponse, @@ -24,6 +26,7 @@ import { } from "../admin-console/models/response/organization-connection.response"; import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response"; import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response"; +import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response"; import { ProviderOrganizationOrganizationDetailsResponse, ProviderOrganizationResponse, @@ -35,7 +38,7 @@ import { ProviderUserUserDetailsResponse, } from "../admin-console/models/response/provider/provider-user.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; -import { CreateAuthRequest } from "../auth/models/request/create-auth.request"; +import { AuthRequest } from "../auth/models/request/auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; @@ -185,8 +188,8 @@ export abstract class ApiService { putUpdateTdeOffboardingPassword: (request: UpdateTdeOffboardingPasswordRequest) => Promise; postConvertToKeyConnector: () => Promise; //passwordless - postAuthRequest: (request: CreateAuthRequest) => Promise; - postAdminAuthRequest: (request: CreateAuthRequest) => Promise; + postAuthRequest: (request: AuthRequest) => Promise; + postAdminAuthRequest: (request: AuthRequest) => Promise; getAuthResponse: (id: string, accessCode: string) => Promise; getAuthRequest: (id: string) => Promise; putAuthRequest: (id: string, request: PasswordlessAuthRequest) => Promise; @@ -490,7 +493,9 @@ export abstract class ApiService { ) => Promise; deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; - postPreValidateSponsorshipToken: (sponsorshipToken: string) => Promise; + postPreValidateSponsorshipToken: ( + sponsorshipToken: string, + ) => Promise; postRedeemSponsorship: ( sponsorshipToken: string, request: OrganizationSponsorshipRedeemRequest, diff --git a/libs/common/src/abstractions/audit.service.ts b/libs/common/src/abstractions/audit.service.ts index cb2a07788af..a54beb59a78 100644 --- a/libs/common/src/abstractions/audit.service.ts +++ b/libs/common/src/abstractions/audit.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BreachAccountResponse } from "../models/response/breach-account.response"; export abstract class AuditService { diff --git a/libs/common/src/abstractions/event/event-collection.service.ts b/libs/common/src/abstractions/event/event-collection.service.ts index 38f226cc2d6..6ca94d93a62 100644 --- a/libs/common/src/abstractions/event/event-collection.service.ts +++ b/libs/common/src/abstractions/event/event-collection.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EventType } from "../../enums"; import { CipherView } from "../../vault/models/view/cipher.view"; diff --git a/libs/common/src/abstractions/event/event-upload.service.ts b/libs/common/src/abstractions/event/event-upload.service.ts index 5b7a98629a6..af2e7a77e7f 100644 --- a/libs/common/src/abstractions/event/event-upload.service.ts +++ b/libs/common/src/abstractions/event/event-upload.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UserId } from "../../types/guid"; export abstract class EventUploadService { diff --git a/libs/common/src/abstractions/notifications.service.ts b/libs/common/src/abstractions/notifications.service.ts index 921e8e62d52..2234a5588a6 100644 --- a/libs/common/src/abstractions/notifications.service.ts +++ b/libs/common/src/abstractions/notifications.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class NotificationsService { init: () => Promise; updateConnection: (sync?: boolean) => Promise; diff --git a/libs/common/src/abstractions/search.service.ts b/libs/common/src/abstractions/search.service.ts index dfcf2c5d078..ae53266cc21 100644 --- a/libs/common/src/abstractions/search.service.ts +++ b/libs/common/src/abstractions/search.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { SendView } from "../tools/send/models/view/send.view"; diff --git a/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts index 69539e6fea0..e1d91462876 100644 --- a/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; diff --git a/libs/common/src/abstractions/vault-timeout/vault-timeout.service.ts b/libs/common/src/abstractions/vault-timeout/vault-timeout.service.ts index 6baadfc0337..cb07c7d193a 100644 --- a/libs/common/src/abstractions/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/abstractions/vault-timeout/vault-timeout.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class VaultTimeoutService { checkVaultTimeout: () => Promise; lock: (userId?: string) => Promise; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts index d7783cfe1c9..5a393ed1996 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts @@ -1,5 +1,6 @@ -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ListResponse } from "../../../models/response/list.response"; import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request"; import { OrganizationDomainSsoDetailsResponse } from "./responses/organization-domain-sso-details.response"; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/org-domain.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization-domain/org-domain.service.abstraction.ts index ba8a7a2dd2d..05a0b6d722f 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/org-domain.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/org-domain.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { OrganizationDomainResponse } from "./responses/organization-domain.response"; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts index c4817306a63..066d2d381e7 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../../models/response/base.response"; export class VerifiedOrganizationDomainSsoDetailsResponse extends BaseResponse { organizationName: string; diff --git a/libs/common/src/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service.ts b/libs/common/src/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service.ts index 2328165e4b2..95ef7269d58 100644 --- a/libs/common/src/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service.ts +++ b/libs/common/src/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; /** diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 051275f7945..000d1655416 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -1,5 +1,5 @@ -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; - +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request"; import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; @@ -11,6 +11,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; @@ -51,11 +52,11 @@ export class OrganizationApiServiceAbstraction { updatePasswordManagerSeats: ( id: string, request: OrganizationSubscriptionUpdateRequest, - ) => Promise; + ) => Promise; updateSecretsManagerSubscription: ( id: string, request: OrganizationSmSubscriptionUpdateRequest, - ) => Promise; + ) => Promise; updateSeats: (id: string, request: SeatRequest) => Promise; updateStorage: (id: string, request: StorageRequest) => Promise; verifyBank: (id: string, request: VerifyBankRequest) => Promise; diff --git a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts index a2ea6aa8861..2161feb516e 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts @@ -1,7 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, Observable } from "rxjs"; -import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { Utils } from "../../../platform/misc/utils"; import { UserId } from "../../../types/guid"; import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; @@ -16,7 +16,8 @@ export function canAccessSettingsTab(org: Organization): boolean { org.canManagePolicies || org.canManageSso || org.canManageScim || - org.canAccessImportExport || + org.canAccessImport || + org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway org.canManageDeviceApprovals ); } @@ -56,32 +57,6 @@ export function getOrganizationById(id: string) { return map((orgs) => orgs.find((o) => o.id === id)); } -export function canAccessAdmin(i18nService: I18nService) { - return map((orgs) => - orgs.filter(canAccessOrgAdmin).sort(Utils.getSortFunction(i18nService, "name")), - ); -} - -/** - * @deprecated - * To be removed after Flexible Collections. - **/ -export function canAccessImportExport(i18nService: I18nService) { - return map((orgs) => - orgs - .filter((org) => org.canAccessImportExport) - .sort(Utils.getSortFunction(i18nService, "name")), - ); -} - -export function canAccessImport(i18nService: I18nService) { - return map((orgs) => - orgs - .filter((org) => org.canAccessImportExport || org.canCreateNewCollections) - .sort(Utils.getSortFunction(i18nService, "name")), - ); -} - /** * Returns `true` if a user is a member of an organization (rather than only being a ProviderUser) * @deprecated Use organizationService.organizations$ with a filter instead diff --git a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts new file mode 100644 index 00000000000..b5c0f6291fc --- /dev/null +++ b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts @@ -0,0 +1,111 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { map, Observable } from "rxjs"; + +import { UserId } from "../../../types/guid"; +import { OrganizationData } from "../../models/data/organization.data"; +import { Organization } from "../../models/domain/organization"; + +export function canAccessVaultTab(org: Organization): boolean { + return org.canViewAllCollections; +} + +export function canAccessSettingsTab(org: Organization): boolean { + return ( + org.isOwner || + org.canManagePolicies || + org.canManageSso || + org.canManageScim || + org.canAccessImport || + org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway + org.canManageDeviceApprovals + ); +} + +export function canAccessMembersTab(org: Organization): boolean { + return org.canManageUsers || org.canManageUsersPassword; +} + +export function canAccessGroupsTab(org: Organization): boolean { + return org.canManageGroups; +} + +export function canAccessReportingTab(org: Organization): boolean { + return org.canAccessReports || org.canAccessEventLogs; +} + +export function canAccessBillingTab(org: Organization): boolean { + return org.isOwner; +} + +export function canAccessOrgAdmin(org: Organization): boolean { + // Admin console can only be accessed by Owners for disabled organizations + if (!org.enabled && !org.isOwner) { + return false; + } + return ( + canAccessMembersTab(org) || + canAccessGroupsTab(org) || + canAccessReportingTab(org) || + canAccessBillingTab(org) || + canAccessSettingsTab(org) || + canAccessVaultTab(org) + ); +} + +export function getOrganizationById(id: string) { + return map((orgs) => orgs.find((o) => o.id === id)); +} + +/** + * Publishes an observable stream of organizations. This service is meant to + * be used widely across Bitwarden as the primary way of fetching organizations. + * Risky operations like updates are isolated to the + * internal extension `InternalOrganizationServiceAbstraction`. + */ +export abstract class vNextOrganizationService { + /** + * Publishes state for all organizations under the specified user. + * @returns An observable list of organizations + */ + organizations$: (userId: UserId) => Observable; + + // @todo Clean these up. Continuing to expand them is not recommended. + // @see https://bitwarden.atlassian.net/browse/AC-2252 + memberOrganizations$: (userId: UserId) => Observable; + /** + * Emits true if the user can create or manage a Free Bitwarden Families sponsorship. + */ + canManageSponsorships$: (userId: UserId) => Observable; + /** + * Emits true if any of the user's organizations have a Free Bitwarden Families sponsorship available. + */ + familySponsorshipAvailable$: (userId: UserId) => Observable; + hasOrganizations: (userId: UserId) => Observable; +} + +/** + * Big scary buttons that **update** organization state. These should only be + * called from within admin-console scoped code. Extends the base + * `OrganizationService` for easy access to `get` calls. + * @internal + */ +export abstract class vNextInternalOrganizationServiceAbstraction extends vNextOrganizationService { + /** + * Replaces state for the provided organization, or creates it if not found. + * @param organization The organization state being saved. + * @param userId The userId to replace state for. + */ + upsert: (OrganizationData: OrganizationData, userId: UserId) => Promise; + + /** + * Replaces state for the entire registered organization list for the specified user. + * You probably don't want this unless you're calling from a full sync + * operation or a logout. See `upsert` for creating & updating a single + * organization in the state. + * @param organizations A complete list of all organization state for the provided + * user. + * @param userId The userId to replace state for. + */ + replace: (organizations: { [id: string]: OrganizationData }, userId: UserId) => Promise; +} diff --git a/libs/common/src/admin-console/abstractions/policy/policy-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/policy/policy-api.service.abstraction.ts index c7fec8813c7..18cc13b29c9 100644 --- a/libs/common/src/admin-console/abstractions/policy/policy-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/policy/policy-api.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ListResponse } from "../../../models/response/list.response"; import { PolicyType } from "../../enums"; import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options"; diff --git a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts index 1067c242346..bed341115ee 100644 --- a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserId } from "../../../types/guid"; diff --git a/libs/common/src/admin-console/abstractions/provider.service.ts b/libs/common/src/admin-console/abstractions/provider.service.ts index 084e0a0c652..0cd21174ea1 100644 --- a/libs/common/src/admin-console/abstractions/provider.service.ts +++ b/libs/common/src/admin-console/abstractions/provider.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts index 3c2170bf9e6..f348e7487de 100644 --- a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request"; import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request"; diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index b05a03bb924..2ca9fbef8db 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -12,4 +12,5 @@ export enum PolicyType { DisablePersonalVaultExport = 10, // Disable personal vault export ActivateAutofill = 11, // Activates autofill with page load on the browser extension AutomaticAppLogIn = 12, // Enables automatic log in of apps from configured identity provider + FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization } diff --git a/libs/common/src/admin-console/models/api/permissions.api.ts b/libs/common/src/admin-console/models/api/permissions.api.ts index e6b435294fc..800ed3d67ee 100644 --- a/libs/common/src/admin-console/models/api/permissions.api.ts +++ b/libs/common/src/admin-console/models/api/permissions.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class PermissionsApi extends BaseResponse { diff --git a/libs/common/src/admin-console/models/api/scim-config.api.ts b/libs/common/src/admin-console/models/api/scim-config.api.ts index ed30224919a..c87f6e108de 100644 --- a/libs/common/src/admin-console/models/api/scim-config.api.ts +++ b/libs/common/src/admin-console/models/api/scim-config.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { ScimProviderType } from "../../enums"; diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index 0b3d512817b..da9a82e7c5c 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -53,11 +53,10 @@ describe("ORGANIZATIONS state", () => { accessSecretsManager: false, limitCollectionCreation: false, limitCollectionDeletion: false, - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - limitCollectionCreationDeletion: false, allowAdminAccessToAllCollectionItems: false, familySponsorshipLastSyncDate: new Date(), userIsManagedByOrganization: false, + useRiskInsights: false, }, }; const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult))); diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index 0c0dedad256..8ec84b5fd09 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { ProductTierType } from "../../../billing/enums"; @@ -54,10 +56,9 @@ export class OrganizationData { accessSecretsManager: boolean; limitCollectionCreation: boolean; limitCollectionDeletion: boolean; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - limitCollectionCreationDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor( response?: ProfileOrganizationResponse, @@ -116,10 +117,9 @@ export class OrganizationData { this.accessSecretsManager = response.accessSecretsManager; this.limitCollectionCreation = response.limitCollectionCreation; this.limitCollectionDeletion = response.limitCollectionDeletion; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - this.limitCollectionCreationDeletion = response.limitCollectionCreationDeletion; this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = response.userIsManagedByOrganization; + this.useRiskInsights = response.useRiskInsights; this.isMember = options.isMember; this.isProviderUser = options.isProviderUser; diff --git a/libs/common/src/admin-console/models/data/policy.data.ts b/libs/common/src/admin-console/models/data/policy.data.ts index 54185c84da9..a8628e2f1ab 100644 --- a/libs/common/src/admin-console/models/data/policy.data.ts +++ b/libs/common/src/admin-console/models/data/policy.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PolicyId } from "../../../types/guid"; import { PolicyType } from "../../enums"; import { Policy } from "../domain/policy"; diff --git a/libs/common/src/admin-console/models/domain/master-password-policy-options.ts b/libs/common/src/admin-console/models/domain/master-password-policy-options.ts index 889a84a4fc9..340b21aaf0d 100644 --- a/libs/common/src/admin-console/models/domain/master-password-policy-options.ts +++ b/libs/common/src/admin-console/models/domain/master-password-policy-options.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MasterPasswordPolicyResponse } from "../../../auth/models/response/master-password-policy.response"; import Domain from "../../../platform/models/domain/domain-base"; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 42436e0a93d..8441298bbff 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { ProductTierType } from "../../../billing/enums"; @@ -70,8 +72,6 @@ export class Organization { */ limitCollectionCreation: boolean; limitCollectionDeletion: boolean; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - limitCollectionCreationDeletion: boolean; /** * Refers to the ability for an owner/admin to access all collection items, regardless of assigned collections @@ -83,6 +83,7 @@ export class Organization { * matches one of the verified domains of that organization, and the user is a member of it. */ userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -137,10 +138,9 @@ export class Organization { this.accessSecretsManager = obj.accessSecretsManager; this.limitCollectionCreation = obj.limitCollectionCreation; this.limitCollectionDeletion = obj.limitCollectionDeletion; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - this.limitCollectionCreationDeletion = obj.limitCollectionCreationDeletion; this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = obj.userIsManagedByOrganization; + this.useRiskInsights = obj.useRiskInsights; } get canAccess() { @@ -168,8 +168,31 @@ export class Organization { return (this.isAdmin || this.permissions.accessEventLogs) && this.useEvents; } - get canAccessImportExport() { - return this.isAdmin || this.permissions.accessImportExport; + /** + * Returns true if the user can access the Import page in the Admin Console. + * Note: this does not affect user access to the Import page in Password Manager, which can also be used to import + * into organization collections. + */ + get canAccessImport() { + return ( + this.isProviderUser || + this.type === OrganizationUserType.Owner || + this.type === OrganizationUserType.Admin || + this.permissions.accessImportExport + ); + } + + canAccessExport(removeProviderExport: boolean) { + if (!removeProviderExport && this.isProviderUser) { + return true; + } + + return ( + this.isMember && + (this.type === OrganizationUserType.Owner || + this.type === OrganizationUserType.Admin || + this.permissions.accessImportExport) + ); } get canAccessReports() { @@ -222,7 +245,7 @@ export class Organization { return true; } - // If AllowAdminAccessToAllCollectionItems is true, Owners and Admins can delete any collection, regardless of LimitCollectionCreationDeletion setting + // If AllowAdminAccessToAllCollectionItems is true, Owners and Admins can delete any collection, regardless of LimitCollectionDeletion setting // Using explicit type checks because provider users are handled above and this mimics the server's permission checks closely if (this.allowAdminAccessToAllCollectionItems) { return this.type == OrganizationUserType.Owner || this.type == OrganizationUserType.Admin; @@ -283,9 +306,7 @@ export class Organization { return true; } - return this.hasProvider && this.providerType === ProviderType.Msp - ? this.isProviderUser - : this.isOwner; + return this.hasBillableProvider ? this.isProviderUser : this.isOwner; } get canEditSubscription() { @@ -304,6 +325,14 @@ export class Organization { return this.providerId != null || this.providerName != null; } + get hasBillableProvider() { + return ( + this.hasProvider && + (this.providerType === ProviderType.Msp || + this.providerType === ProviderType.MultiOrganizationEnterprise) + ); + } + get hasReseller() { return this.hasProvider && this.providerType === ProviderType.Reseller; } @@ -331,4 +360,15 @@ export class Organization { familySponsorshipValidUntil: new Date(json.familySponsorshipValidUntil), }); } + + get canAccessIntegrations() { + return ( + (this.productTierType === ProductTierType.Teams || + this.productTierType === ProductTierType.Enterprise) && + (this.isAdmin || + this.permissions.manageUsers || + this.permissions.manageGroups || + this.permissions.accessEventLogs) + ); + } } diff --git a/libs/common/src/admin-console/models/domain/policy.ts b/libs/common/src/admin-console/models/domain/policy.ts index c2f9c9c8dfc..b45acb9920d 100644 --- a/libs/common/src/admin-console/models/domain/policy.ts +++ b/libs/common/src/admin-console/models/domain/policy.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ListResponse } from "../../../models/response/list.response"; import Domain from "../../../platform/models/domain/domain-base"; import { PolicyId } from "../../../types/guid"; diff --git a/libs/common/src/admin-console/models/domain/provider.ts b/libs/common/src/admin-console/models/domain/provider.ts index d51f6985477..a70cb72995a 100644 --- a/libs/common/src/admin-console/models/domain/provider.ts +++ b/libs/common/src/admin-console/models/domain/provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ProviderStatusType, ProviderUserStatusType, ProviderUserType } from "../../enums"; import { ProviderData } from "../data/provider.data"; diff --git a/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts b/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts index baf9a2ca458..23c39376d71 100644 --- a/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts +++ b/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts @@ -1,7 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class OrganizationCollectionManagementUpdateRequest { limitCollectionCreation: boolean; limitCollectionDeletion: boolean; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - limitCreateDeleteOwnerAdmin: boolean; allowAdminAccessToAllCollectionItems: boolean; } diff --git a/libs/common/src/admin-console/models/request/organization-create.request.ts b/libs/common/src/admin-console/models/request/organization-create.request.ts index 98f19bebaf4..e8561307b20 100644 --- a/libs/common/src/admin-console/models/request/organization-create.request.ts +++ b/libs/common/src/admin-console/models/request/organization-create.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PaymentMethodType } from "../../../billing/enums"; import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/models/request/organization-no-payment-method-create-request"; diff --git a/libs/common/src/admin-console/models/request/organization-update.request.ts b/libs/common/src/admin-console/models/request/organization-update.request.ts index 98d5d0338bc..1cde23dc675 100644 --- a/libs/common/src/admin-console/models/request/organization-update.request.ts +++ b/libs/common/src/admin-console/models/request/organization-update.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationKeysRequest } from "./organization-keys.request"; export class OrganizationUpdateRequest { diff --git a/libs/common/src/admin-console/models/request/organization-upgrade.request.ts b/libs/common/src/admin-console/models/request/organization-upgrade.request.ts index eba897f31b6..69190b43df6 100644 --- a/libs/common/src/admin-console/models/request/organization-upgrade.request.ts +++ b/libs/common/src/admin-console/models/request/organization-upgrade.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PlanType } from "../../../billing/enums"; import { OrganizationKeysRequest } from "./organization-keys.request"; diff --git a/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts b/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts index 7fd0dfb14b7..534afffd1bb 100644 --- a/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts +++ b/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PlanSponsorshipType } from "../../../../billing/enums"; export class OrganizationSponsorshipCreateRequest { diff --git a/libs/common/src/admin-console/models/request/organization/organization-sponsorship-redeem.request.ts b/libs/common/src/admin-console/models/request/organization/organization-sponsorship-redeem.request.ts index bbca7d559a9..322b80651b0 100644 --- a/libs/common/src/admin-console/models/request/organization/organization-sponsorship-redeem.request.ts +++ b/libs/common/src/admin-console/models/request/organization/organization-sponsorship-redeem.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PlanSponsorshipType } from "../../../../billing/enums"; export class OrganizationSponsorshipRedeemRequest { diff --git a/libs/common/src/admin-console/models/request/policy.request.ts b/libs/common/src/admin-console/models/request/policy.request.ts index 413cd502a59..0f3b1be7d88 100644 --- a/libs/common/src/admin-console/models/request/policy.request.ts +++ b/libs/common/src/admin-console/models/request/policy.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PolicyType } from "../../enums"; export class PolicyRequest { diff --git a/libs/common/src/admin-console/models/request/provider/provider-add-organization.request.ts b/libs/common/src/admin-console/models/request/provider/provider-add-organization.request.ts index 380eea1d60d..cc61e358ede 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-add-organization.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-add-organization.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ProviderAddOrganizationRequest { organizationId: string; key: string; diff --git a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts index 7dc664869c1..d4992e969dc 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ExpandedTaxInfoUpdateRequest } from "../../../../billing/models/request/expanded-tax-info-update.request"; export class ProviderSetupRequest { diff --git a/libs/common/src/admin-console/models/request/provider/provider-update.request.ts b/libs/common/src/admin-console/models/request/provider/provider-update.request.ts index dafa7418a38..4503d41308f 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-update.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-update.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ProviderUpdateRequest { name: string; businessName: string; diff --git a/libs/common/src/admin-console/models/request/provider/provider-user-accept.request.ts b/libs/common/src/admin-console/models/request/provider/provider-user-accept.request.ts index 0435e1df083..7db1889bf36 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-user-accept.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-user-accept.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ProviderUserAcceptRequest { token: string; } diff --git a/libs/common/src/admin-console/models/request/provider/provider-user-confirm.request.ts b/libs/common/src/admin-console/models/request/provider/provider-user-confirm.request.ts index 1b7d4a0615e..bdcd91f8de9 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-user-confirm.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-user-confirm.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ProviderUserConfirmRequest { key: string; } diff --git a/libs/common/src/admin-console/models/request/provider/provider-user-invite.request.ts b/libs/common/src/admin-console/models/request/provider/provider-user-invite.request.ts index dc3839b2821..97c710693a0 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-user-invite.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-user-invite.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ProviderUserType } from "../../../enums"; export class ProviderUserInviteRequest { diff --git a/libs/common/src/admin-console/models/request/provider/provider-user-update.request.ts b/libs/common/src/admin-console/models/request/provider/provider-user-update.request.ts index 6f55f35b1ac..b2f9b2d0faf 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-user-update.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-user-update.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ProviderUserType } from "../../../enums"; export class ProviderUserUpdateRequest { diff --git a/libs/common/src/admin-console/models/request/scim-config.request.ts b/libs/common/src/admin-console/models/request/scim-config.request.ts index 4a46c7d8b9f..3022b7273a0 100644 --- a/libs/common/src/admin-console/models/request/scim-config.request.ts +++ b/libs/common/src/admin-console/models/request/scim-config.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ScimProviderType } from "../../enums"; export class ScimConfigRequest { diff --git a/libs/common/src/admin-console/models/response/organization-connection.response.ts b/libs/common/src/admin-console/models/response/organization-connection.response.ts index b694e48cc60..251c71cc052 100644 --- a/libs/common/src/admin-console/models/response/organization-connection.response.ts +++ b/libs/common/src/admin-console/models/response/organization-connection.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BillingSyncConfigApi } from "../../../billing/models/api/billing-sync-config.api"; import { BaseResponse } from "../../../models/response/base.response"; import { OrganizationConnectionType } from "../../enums"; diff --git a/libs/common/src/admin-console/models/response/organization-export.response.ts b/libs/common/src/admin-console/models/response/organization-export.response.ts index 8ead32d782f..6e42fe14c0d 100644 --- a/libs/common/src/admin-console/models/response/organization-export.response.ts +++ b/libs/common/src/admin-console/models/response/organization-export.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CollectionResponse } from "@bitwarden/admin-console/common"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/admin-console/models/response/organization-sponsorship.response.ts b/libs/common/src/admin-console/models/response/organization-sponsorship.response.ts new file mode 100644 index 00000000000..31f7f5907ec --- /dev/null +++ b/libs/common/src/admin-console/models/response/organization-sponsorship.response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class OrganizationSponsorshipResponse extends BaseResponse { + isPolicyEnabled: string; + + constructor(response: any) { + super(response); + this.isPolicyEnabled = this.getResponseProperty("IsPolicyEnabled"); + } +} diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts index aaa28e48a5c..fd54ff128b6 100644 --- a/libs/common/src/admin-console/models/response/organization.response.ts +++ b/libs/common/src/admin-console/models/response/organization.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PlanType } from "../../../billing/enums"; import { PlanResponse } from "../../../billing/models/response/plan.response"; import { BaseResponse } from "../../../models/response/base.response"; @@ -34,9 +36,8 @@ export class OrganizationResponse extends BaseResponse { maxAutoscaleSmServiceAccounts?: number; limitCollectionCreation: boolean; limitCollectionDeletion: boolean; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - limitCollectionCreationDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; + useRiskInsights: boolean; constructor(response: any) { super(response); @@ -74,12 +75,9 @@ export class OrganizationResponse extends BaseResponse { this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts"); this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation"); this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion"); - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - this.limitCollectionCreationDeletion = this.getResponseProperty( - "LimitCollectionCreationDeletion", - ); this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); + this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); } } diff --git a/libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts b/libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts new file mode 100644 index 00000000000..53855c84097 --- /dev/null +++ b/libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts @@ -0,0 +1,12 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class PreValidateSponsorshipResponse extends BaseResponse { + isTokenValid: boolean; + isFreeFamilyPolicyEnabled: boolean; + + constructor(response: any) { + super(response); + this.isTokenValid = this.getResponseProperty("IsTokenValid"); + this.isFreeFamilyPolicyEnabled = this.getResponseProperty("IsFreeFamilyPolicyEnabled"); + } +} diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index 4d9366e6627..9c4b8885ab8 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -51,10 +51,9 @@ export class ProfileOrganizationResponse extends BaseResponse { accessSecretsManager: boolean; limitCollectionCreation: boolean; limitCollectionDeletion: boolean; - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - limitCollectionCreationDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor(response: any) { super(response); @@ -115,13 +114,10 @@ export class ProfileOrganizationResponse extends BaseResponse { this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager"); this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation"); this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion"); - // Deprecated: https://bitwarden.atlassian.net/browse/PM-10863 - this.limitCollectionCreationDeletion = this.getResponseProperty( - "LimitCollectionCreationDeletion", - ); this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); this.userIsManagedByOrganization = this.getResponseProperty("UserIsManagedByOrganization"); + this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); } } diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts index 7497a77e6f2..1052fe504d4 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts @@ -1,14 +1,13 @@ import { mock } from "jest-mock-extended"; import { lastValueFrom } from "rxjs"; -import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { OrganizationDomainResponse } from "../../abstractions/organization-domain/responses/organization-domain.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { OrgDomainApiService } from "./org-domain-api.service"; import { OrgDomainService } from "./org-domain.service"; diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain.service.ts b/libs/common/src/admin-console/services/organization-domain/org-domain.service.ts index ebdc098c855..0ffa106d001 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain.service.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BehaviorSubject } from "rxjs"; import { I18nService } from "../../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/admin-console/services/organization/default-vnext-organization.service.spec.ts b/libs/common/src/admin-console/services/organization/default-vnext-organization.service.spec.ts new file mode 100644 index 00000000000..9e2ea3a4599 --- /dev/null +++ b/libs/common/src/admin-console/services/organization/default-vnext-organization.service.spec.ts @@ -0,0 +1,204 @@ +import { firstValueFrom } from "rxjs"; + +import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; +import { Utils } from "../../../platform/misc/utils"; +import { OrganizationId, UserId } from "../../../types/guid"; +import { OrganizationData } from "../../models/data/organization.data"; +import { Organization } from "../../models/domain/organization"; + +import { DefaultvNextOrganizationService } from "./default-vnext-organization.service"; +import { ORGANIZATIONS } from "./vnext-organization.state"; + +describe("OrganizationService", () => { + let organizationService: DefaultvNextOrganizationService; + + const fakeUserId = Utils.newGuid() as UserId; + let fakeStateProvider: FakeStateProvider; + + /** + * It is easier to read arrays than records in code, but we store a record + * in state. This helper methods lets us build organization arrays in tests + * and easily map them to records before storing them in state. + */ + function arrayToRecord(input: OrganizationData[]): Record { + if (input == null) { + return undefined; + } + return Object.fromEntries(input?.map((i) => [i.id, i])); + } + + /** + * There are a few assertions in this spec that check for array equality + * but want to ignore a specific index that _should_ be different. This + * function takes two arrays, and an index. It checks for equality of the + * arrays, but splices out the specified index from both arrays first. + */ + function expectIsEqualExceptForIndex(x: any[], y: any[], indexToExclude: number) { + // Clone the arrays to avoid modifying the reference values + const a = [...x]; + const b = [...y]; + delete a[indexToExclude]; + delete b[indexToExclude]; + expect(a).toEqual(b); + } + + /** + * Builds a simple mock `OrganizationData[]` array that can be used in tests + * to populate state. + * @param count The number of organizations to populate the list with. The + * function returns undefined if this is less than 1. The default value is 1. + * @param suffix A string to append to data fields on each organization. + * This defaults to the index of the organization in the list. + * @returns an `OrganizationData[]` array that can be used to populate + * stateProvider. + */ + function buildMockOrganizations(count = 1, suffix?: string): OrganizationData[] { + if (count < 1) { + return undefined; + } + + function buildMockOrganization(id: OrganizationId, name: string, identifier: string) { + const data = new OrganizationData({} as any, {} as any); + data.id = id; + data.name = name; + data.identifier = identifier; + + return data; + } + + const mockOrganizations = []; + for (let i = 0; i < count; i++) { + const s = suffix ? suffix + i.toString() : i.toString(); + mockOrganizations.push( + buildMockOrganization(("org" + s) as OrganizationId, "org" + s, "orgIdentifier" + s), + ); + } + + return mockOrganizations; + } + + const setOrganizationsState = (organizationData: OrganizationData[] | null) => + fakeStateProvider.setUserState( + ORGANIZATIONS, + organizationData == null ? null : arrayToRecord(organizationData), + fakeUserId, + ); + + beforeEach(async () => { + fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(fakeUserId)); + organizationService = new DefaultvNextOrganizationService(fakeStateProvider); + }); + + describe("canManageSponsorships", () => { + it("can because one is available", async () => { + const mockData: OrganizationData[] = buildMockOrganizations(1); + mockData[0].familySponsorshipAvailable = true; + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.canManageSponsorships$(fakeUserId)); + expect(result).toBe(true); + }); + + it("can because one is used", async () => { + const mockData: OrganizationData[] = buildMockOrganizations(1); + mockData[0].familySponsorshipFriendlyName = "Something"; + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.canManageSponsorships$(fakeUserId)); + expect(result).toBe(true); + }); + + it("can not because one isn't available or taken", async () => { + const mockData: OrganizationData[] = buildMockOrganizations(1); + mockData[0].familySponsorshipFriendlyName = null; + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.canManageSponsorships$(fakeUserId)); + expect(result).toBe(false); + }); + }); + + describe("organizations$", () => { + describe("null checking behavior", () => { + it("publishes an empty array if organizations in state = undefined", async () => { + const mockData: OrganizationData[] = undefined; + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result).toEqual([]); + }); + + it("publishes an empty array if organizations in state = null", async () => { + const mockData: OrganizationData[] = null; + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result).toEqual([]); + }); + + it("publishes an empty array if organizations in state = []", async () => { + const mockData: OrganizationData[] = []; + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result).toEqual([]); + }); + + it("returns state for a user", async () => { + const mockData = buildMockOrganizations(10); + await setOrganizationsState(mockData); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result).toEqual(mockData); + }); + }); + }); + + describe("upsert()", () => { + it("can create the organization list if necassary", async () => { + // Notice that no default state is provided in this test, so the list in + // `stateProvider` will be null when the `upsert` method is called. + const mockData = buildMockOrganizations(); + await organizationService.upsert(mockData[0], fakeUserId); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result).toEqual(mockData.map((x) => new Organization(x))); + }); + + it("updates an organization that already exists in state", async () => { + const mockData = buildMockOrganizations(10); + await setOrganizationsState(mockData); + const indexToUpdate = 5; + const anUpdatedOrganization = { + ...buildMockOrganizations(1, "UPDATED").pop(), + id: mockData[indexToUpdate].id, + }; + await organizationService.upsert(anUpdatedOrganization, fakeUserId); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result[indexToUpdate]).not.toEqual(new Organization(mockData[indexToUpdate])); + expect(result[indexToUpdate].id).toEqual(new Organization(mockData[indexToUpdate]).id); + expectIsEqualExceptForIndex( + result, + mockData.map((x) => new Organization(x)), + indexToUpdate, + ); + }); + }); + + describe("replace()", () => { + it("replaces the entire organization list in state", async () => { + const originalData = buildMockOrganizations(10); + await setOrganizationsState(originalData); + + const newData = buildMockOrganizations(10, "newData"); + await organizationService.replace(arrayToRecord(newData), fakeUserId); + + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + + expect(result).toEqual(newData); + expect(result).not.toEqual(originalData); + }); + + // This is more or less a test for logouts + it("can replace state with null", async () => { + const originalData = buildMockOrganizations(2); + await setOrganizationsState(originalData); + await organizationService.replace(null, fakeUserId); + const result = await firstValueFrom(organizationService.organizations$(fakeUserId)); + expect(result).toEqual([]); + expect(result).not.toEqual(originalData); + }); + }); +}); diff --git a/libs/common/src/admin-console/services/organization/default-vnext-organization.service.ts b/libs/common/src/admin-console/services/organization/default-vnext-organization.service.ts new file mode 100644 index 00000000000..8b73c271daf --- /dev/null +++ b/libs/common/src/admin-console/services/organization/default-vnext-organization.service.ts @@ -0,0 +1,103 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { map, Observable } from "rxjs"; + +import { StateProvider } from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { vNextInternalOrganizationServiceAbstraction } from "../../abstractions/organization/vnext.organization.service"; +import { OrganizationData } from "../../models/data/organization.data"; +import { Organization } from "../../models/domain/organization"; + +import { ORGANIZATIONS } from "./vnext-organization.state"; + +/** + * Filter out organizations from an observable that __do not__ offer a + * families-for-enterprise sponsorship to members. + * @returns a function that can be used in `Observable` pipes, + * like `organizationService.organizations$` + */ +function mapToExcludeOrganizationsWithoutFamilySponsorshipSupport() { + return map((orgs) => orgs.filter((o) => o.canManageSponsorships)); +} + +/** + * Filter out organizations from an observable that the organization user + * __is not__ a direct member of. This will exclude organizations only + * accessible as a provider. + * @returns a function that can be used in `Observable` pipes, + * like `organizationService.organizations$` + */ +function mapToExcludeProviderOrganizations() { + return map((orgs) => orgs.filter((o) => o.isMember)); +} + +/** + * Map an observable stream of organizations down to a boolean indicating + * if any organizations exist (`orgs.length > 0`). + * @returns a function that can be used in `Observable` pipes, + * like `organizationService.organizations$` + */ +function mapToBooleanHasAnyOrganizations() { + return map((orgs) => orgs.length > 0); +} + +export class DefaultvNextOrganizationService + implements vNextInternalOrganizationServiceAbstraction +{ + memberOrganizations$(userId: UserId): Observable { + return this.organizations$(userId).pipe(mapToExcludeProviderOrganizations()); + } + + constructor(private stateProvider: StateProvider) {} + + canManageSponsorships$(userId: UserId) { + return this.organizations$(userId).pipe( + mapToExcludeOrganizationsWithoutFamilySponsorshipSupport(), + mapToBooleanHasAnyOrganizations(), + ); + } + + familySponsorshipAvailable$(userId: UserId) { + return this.organizations$(userId).pipe( + map((orgs) => orgs.some((o) => o.familySponsorshipAvailable)), + ); + } + + hasOrganizations(userId: UserId): Observable { + return this.organizations$(userId).pipe(mapToBooleanHasAnyOrganizations()); + } + + async upsert(organization: OrganizationData, userId: UserId): Promise { + await this.organizationState(userId).update((existingOrganizations) => { + const organizations = existingOrganizations ?? {}; + organizations[organization.id] = organization; + return organizations; + }); + } + + async replace(organizations: { [id: string]: OrganizationData }, userId: UserId): Promise { + await this.organizationState(userId).update(() => organizations); + } + + organizations$(userId: UserId): Observable { + return this.organizationState(userId).state$.pipe(this.mapOrganizationRecordToArray()); + } + + private organizationState(userId: UserId) { + return this.stateProvider.getUser(userId, ORGANIZATIONS); + } + + /** + * Accepts a record of `OrganizationData`, which is how we store the + * organization list as a JSON object on disk, to an array of + * `Organization`, which is how the data is published to callers of the + * service. + * @returns a function that can be used to pipe organization data from + * stored state to an exposed object easily consumable by others. + */ + private mapOrganizationRecordToArray() { + return map, Organization[]>((orgs) => + Object.values(orgs ?? {})?.map((o) => new Organization(o)), + ); + } +} diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index a2259d73cc5..598bb2a29db 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -1,4 +1,5 @@ -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ApiService } from "../../../abstractions/api.service"; import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; @@ -12,6 +13,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; @@ -159,27 +161,29 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction async updatePasswordManagerSeats( id: string, request: OrganizationSubscriptionUpdateRequest, - ): Promise { - return this.apiService.send( + ): Promise { + const r = await this.apiService.send( "POST", "/organizations/" + id + "/subscription", request, true, - false, + true, ); + return new ProfileOrganizationResponse(r); } async updateSecretsManagerSubscription( id: string, request: OrganizationSmSubscriptionUpdateRequest, - ): Promise { - return this.apiService.send( + ): Promise { + const r = await this.apiService.send( "POST", "/organizations/" + id + "/sm-subscription", request, true, - false, + true, ); + return new ProfileOrganizationResponse(r); } async updateSeats(id: string, request: SeatRequest): Promise { diff --git a/libs/common/src/admin-console/services/organization/organization.service.ts b/libs/common/src/admin-console/services/organization/organization.service.ts index 91bfcbd0d5d..49e906bdac2 100644 --- a/libs/common/src/admin-console/services/organization/organization.service.ts +++ b/libs/common/src/admin-console/services/organization/organization.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, Observable, firstValueFrom } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts b/libs/common/src/admin-console/services/organization/vnext-organization.state.ts new file mode 100644 index 00000000000..fea0423f389 --- /dev/null +++ b/libs/common/src/admin-console/services/organization/vnext-organization.state.ts @@ -0,0 +1,19 @@ +import { Jsonify } from "type-fest"; + +import { ORGANIZATIONS_DISK, UserKeyDefinition } from "../../../platform/state"; +import { OrganizationData } from "../../models/data/organization.data"; + +/** + * The `KeyDefinition` for accessing organization lists in application state. + * @todo Ideally this wouldn't require a `fromJSON()` call, but `OrganizationData` + * has some properties that contain functions. This should probably get + * cleaned up. + */ +export const ORGANIZATIONS = UserKeyDefinition.record( + ORGANIZATIONS_DISK, + "organizations", + { + deserializer: (obj: Jsonify) => OrganizationData.fromJSON(obj), + clearOn: ["logout"], + }, +); diff --git a/libs/common/src/admin-console/services/policy/policy.service.ts b/libs/common/src/admin-console/services/policy/policy.service.ts index f52d061ad9c..7a04ba38aa7 100644 --- a/libs/common/src/admin-console/services/policy/policy.service.ts +++ b/libs/common/src/admin-console/services/policy/policy.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatest, firstValueFrom, map, Observable, of } from "rxjs"; import { UserKeyDefinition, POLICIES_DISK, StateProvider } from "../../../platform/state"; @@ -238,6 +240,9 @@ export class PolicyService implements InternalPolicyServiceAbstraction { case PolicyType.PersonalOwnership: // individual vault policy applies to everyone except admins and owners return organization.isAdmin; + case PolicyType.FreeFamiliesSponsorshipPolicy: + // free Bitwarden families policy applies to everyone + return false; default: return organization.canManagePolicies; } diff --git a/libs/common/src/admin-console/services/provider.service.ts b/libs/common/src/admin-console/services/provider.service.ts index 4a462316b95..05909f9d8a1 100644 --- a/libs/common/src/admin-console/services/provider.service.ts +++ b/libs/common/src/admin-console/services/provider.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, Observable, of, switchMap, take } from "rxjs"; import { PROVIDERS_DISK, StateProvider, UserKeyDefinition } from "../../platform/state"; diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts index aab935817a9..094e005e656 100644 --- a/libs/common/src/auth/abstractions/account.service.ts +++ b/libs/common/src/auth/abstractions/account.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/auth/abstractions/anonymous-hub.service.ts b/libs/common/src/auth/abstractions/anonymous-hub.service.ts index e108dccbb60..8e705d67bfe 100644 --- a/libs/common/src/auth/abstractions/anonymous-hub.service.ts +++ b/libs/common/src/auth/abstractions/anonymous-hub.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class AnonymousHubService { createHubConnection: (token: string) => Promise; stopHubConnection: () => Promise; diff --git a/libs/common/src/auth/abstractions/avatar.service.ts b/libs/common/src/auth/abstractions/avatar.service.ts index 7da92ac7fdb..89729aa3712 100644 --- a/libs/common/src/auth/abstractions/avatar.service.ts +++ b/libs/common/src/auth/abstractions/avatar.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts b/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts index 4c52945554c..13963b03bea 100644 --- a/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { EncString } from "../../platform/models/domain/enc-string"; diff --git a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts index 8c3d0c3015d..92f0ebf1667 100644 --- a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ListResponse } from "../../models/response/list.response"; import { DeviceResponse } from "../abstractions/devices/responses/device.response"; import { SecretVerificationRequest } from "../models/request/secret-verification.request"; @@ -34,4 +36,10 @@ export abstract class DevicesApiServiceAbstraction { * @param deviceIdentifier - current device identifier */ postDeviceTrustLoss: (deviceIdentifier: string) => Promise; + + /** + * Deactivates a device + * @param deviceId - The device ID + */ + deactivateDevice: (deviceId: string) => Promise; } diff --git a/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts b/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts index 56216b3d94e..ba6890947c1 100644 --- a/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts @@ -1,15 +1,18 @@ import { Observable } from "rxjs"; +import { DeviceResponse } from "./responses/device.response"; import { DeviceView } from "./views/device.view"; export abstract class DevicesServiceAbstraction { - getDevices$: () => Observable>; - getDeviceByIdentifier$: (deviceIdentifier: string) => Observable; - isDeviceKnownForUser$: (email: string, deviceIdentifier: string) => Observable; - updateTrustedDeviceKeys$: ( + abstract getDevices$(): Observable>; + abstract getDeviceByIdentifier$(deviceIdentifier: string): Observable; + abstract isDeviceKnownForUser$(email: string, deviceIdentifier: string): Observable; + abstract updateTrustedDeviceKeys$( deviceIdentifier: string, devicePublicKeyEncryptedUserKey: string, userKeyEncryptedDevicePublicKey: string, deviceKeyEncryptedDevicePrivateKey: string, - ) => Observable; + ): Observable; + abstract deactivateDevice$(deviceId: string): Observable; + abstract getCurrentDevice$(): Observable; } diff --git a/libs/common/src/auth/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts index a4e40037b05..84a2fb03c28 100644 --- a/libs/common/src/auth/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -1,6 +1,11 @@ import { DeviceType } from "../../../../enums"; import { BaseResponse } from "../../../../models/response/base.response"; +export interface DevicePendingAuthRequest { + id: string; + creationDate: string; +} + export class DeviceResponse extends BaseResponse { id: string; userId: string; @@ -9,6 +14,9 @@ export class DeviceResponse extends BaseResponse { type: DeviceType; creationDate: string; revisionDate: string; + isTrusted: boolean; + devicePendingAuthRequest: DevicePendingAuthRequest | null; + constructor(response: any) { super(response); this.id = this.getResponseProperty("Id"); @@ -18,5 +26,7 @@ export class DeviceResponse extends BaseResponse { this.type = this.getResponseProperty("Type"); this.creationDate = this.getResponseProperty("CreationDate"); this.revisionDate = this.getResponseProperty("RevisionDate"); + this.isTrusted = this.getResponseProperty("IsTrusted"); + this.devicePendingAuthRequest = this.getResponseProperty("DevicePendingAuthRequest"); } } diff --git a/libs/common/src/auth/abstractions/devices/views/device.view.ts b/libs/common/src/auth/abstractions/devices/views/device.view.ts index ce76c77a93b..22e522b9eb0 100644 --- a/libs/common/src/auth/abstractions/devices/views/device.view.ts +++ b/libs/common/src/auth/abstractions/devices/views/device.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DeviceType } from "../../../../enums"; import { View } from "../../../../models/view/view"; import { DeviceResponse } from "../responses/device.response"; @@ -10,6 +12,7 @@ export class DeviceView implements View { type: DeviceType; creationDate: string; revisionDate: string; + response: DeviceResponse; constructor(deviceResponse: DeviceResponse) { Object.assign(this, deviceResponse); diff --git a/libs/common/src/auth/abstractions/key-connector.service.ts b/libs/common/src/auth/abstractions/key-connector.service.ts index 26335ced489..9bcca1f5619 100644 --- a/libs/common/src/auth/abstractions/key-connector.service.ts +++ b/libs/common/src/auth/abstractions/key-connector.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Organization } from "../../admin-console/models/domain/organization"; import { UserId } from "../../types/guid"; import { IdentityTokenResponse } from "../models/response/identity-token.response"; diff --git a/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts b/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts index c964c8809c3..3f3731e0e1b 100644 --- a/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class SsoLoginServiceAbstraction { /** * Gets the code verifier used for SSO. diff --git a/libs/common/src/auth/abstractions/token.service.ts b/libs/common/src/auth/abstractions/token.service.ts index 9239a0db543..4695e45e658 100644 --- a/libs/common/src/auth/abstractions/token.service.ts +++ b/libs/common/src/auth/abstractions/token.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; diff --git a/libs/common/src/auth/abstractions/two-factor.service.ts b/libs/common/src/auth/abstractions/two-factor.service.ts index a0a9ecd2afb..00987dabd98 100644 --- a/libs/common/src/auth/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/abstractions/two-factor.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TwoFactorProviderType } from "../enums/two-factor-provider-type"; import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; diff --git a/libs/common/src/auth/abstractions/user-verification/user-verification-api.service.abstraction.ts b/libs/common/src/auth/abstractions/user-verification/user-verification-api.service.abstraction.ts index ae17abe823e..42abc794061 100644 --- a/libs/common/src/auth/abstractions/user-verification/user-verification-api.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/user-verification/user-verification-api.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; import { VerifyOTPRequest } from "../../models/request/verify-otp.request"; import { MasterPasswordPolicyResponse } from "../../models/response/master-password-policy.response"; diff --git a/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts b/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts index fd04b2e2c59..2d39854f8d9 100644 --- a/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UserId } from "../../../types/guid"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; import { UserVerificationOptions } from "../../types/user-verification-options"; diff --git a/libs/common/src/auth/abstractions/webauthn/webauthn-login-api.service.abstraction.ts b/libs/common/src/auth/abstractions/webauthn/webauthn-login-api.service.abstraction.ts index 01845a2e3d8..ca87710d22f 100644 --- a/libs/common/src/auth/abstractions/webauthn/webauthn-login-api.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/webauthn/webauthn-login-api.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CredentialAssertionOptionsResponse } from "../../services/webauthn-login/response/credential-assertion-options.response"; export class WebAuthnLoginApiServiceAbstraction { diff --git a/libs/common/src/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction.ts b/libs/common/src/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction.ts index c3c09466091..5de89313ecc 100644 --- a/libs/common/src/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PrfKey } from "../../../types/key"; /** diff --git a/libs/common/src/auth/abstractions/webauthn/webauthn-login.service.abstraction.ts b/libs/common/src/auth/abstractions/webauthn/webauthn-login.service.abstraction.ts index 81ee1e88294..8e6ffae27a8 100644 --- a/libs/common/src/auth/abstractions/webauthn/webauthn-login.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/webauthn/webauthn-login.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AuthResult } from "../../models/domain/auth-result"; import { WebAuthnLoginCredentialAssertionOptionsView } from "../../models/view/webauthn-login/webauthn-login-credential-assertion-options.view"; import { WebAuthnLoginCredentialAssertionView } from "../../models/view/webauthn-login/webauthn-login-credential-assertion.view"; diff --git a/libs/common/src/auth/captcha-iframe.ts b/libs/common/src/auth/captcha-iframe.ts index 9a49532710b..94859003e16 100644 --- a/libs/common/src/auth/captcha-iframe.ts +++ b/libs/common/src/auth/captcha-iframe.ts @@ -2,6 +2,7 @@ import { I18nService } from "../platform/abstractions/i18n.service"; import { IFrameComponent } from "./iframe-component"; +// TODO: PM-15162 - captcha is deprecated as part of UI refresh work export class CaptchaIFrame extends IFrameComponent { constructor( win: Window, diff --git a/libs/common/src/auth/iframe-component.ts b/libs/common/src/auth/iframe-component.ts index 3d2d3c7ac17..814dc9642da 100644 --- a/libs/common/src/auth/iframe-component.ts +++ b/libs/common/src/auth/iframe-component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class IFrameComponent { iframe: HTMLIFrameElement; private connectorLink: HTMLAnchorElement; diff --git a/libs/common/src/auth/models/api/sso-config.api.ts b/libs/common/src/auth/models/api/sso-config.api.ts index 138bd139073..ee76135effd 100644 --- a/libs/common/src/auth/models/api/sso-config.api.ts +++ b/libs/common/src/auth/models/api/sso-config.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { MemberDecryptionType, diff --git a/libs/common/src/auth/models/domain/admin-auth-req-storable.ts b/libs/common/src/auth/models/domain/admin-auth-req-storable.ts index df0341ac163..bdb74ebbc6b 100644 --- a/libs/common/src/auth/models/domain/admin-auth-req-storable.ts +++ b/libs/common/src/auth/models/domain/admin-auth-req-storable.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Utils } from "../../../platform/misc/utils"; diff --git a/libs/common/src/auth/models/domain/auth-result.ts b/libs/common/src/auth/models/domain/auth-result.ts index bc828d3e86d..1c176c2b84b 100644 --- a/libs/common/src/auth/models/domain/auth-result.ts +++ b/libs/common/src/auth/models/domain/auth-result.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Utils } from "../../../platform/misc/utils"; import { UserId } from "../../../types/guid"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; diff --git a/libs/common/src/auth/models/request/create-auth.request.ts b/libs/common/src/auth/models/request/auth.request.ts similarity index 88% rename from libs/common/src/auth/models/request/create-auth.request.ts rename to libs/common/src/auth/models/request/auth.request.ts index ab0c512080d..c992af6c1f8 100644 --- a/libs/common/src/auth/models/request/create-auth.request.ts +++ b/libs/common/src/auth/models/request/auth.request.ts @@ -1,6 +1,6 @@ import { AuthRequestType } from "../../enums/auth-request-type"; -export class CreateAuthRequest { +export class AuthRequest { constructor( readonly email: string, readonly deviceIdentifier: string, diff --git a/libs/common/src/auth/models/request/captcha-protected.request.ts b/libs/common/src/auth/models/request/captcha-protected.request.ts index 54721e01477..f15f4efebeb 100644 --- a/libs/common/src/auth/models/request/captcha-protected.request.ts +++ b/libs/common/src/auth/models/request/captcha-protected.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class CaptchaProtectedRequest { captchaResponse: string = null; } diff --git a/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts index 7a9d7588464..37337720e1f 100644 --- a/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts +++ b/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TwoFactorProviderRequest } from "./two-factor-provider.request"; export class DisableTwoFactorAuthenticatorRequest extends TwoFactorProviderRequest { diff --git a/libs/common/src/auth/models/request/email-token.request.ts b/libs/common/src/auth/models/request/email-token.request.ts index 04d50dda7f8..71d7d68b208 100644 --- a/libs/common/src/auth/models/request/email-token.request.ts +++ b/libs/common/src/auth/models/request/email-token.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class EmailTokenRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/email.request.ts b/libs/common/src/auth/models/request/email.request.ts index 8b7b842721f..12fdff149cc 100644 --- a/libs/common/src/auth/models/request/email.request.ts +++ b/libs/common/src/auth/models/request/email.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EmailTokenRequest } from "./email-token.request"; export class EmailRequest extends EmailTokenRequest { diff --git a/libs/common/src/auth/models/request/identity-token/device.request.ts b/libs/common/src/auth/models/request/identity-token/device.request.ts index 944ac8c7890..2ad2fbd5ebd 100644 --- a/libs/common/src/auth/models/request/identity-token/device.request.ts +++ b/libs/common/src/auth/models/request/identity-token/device.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { DeviceType } from "../../../../enums"; diff --git a/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts b/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts index a535b9aa303..b692ddfe37c 100644 --- a/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts +++ b/libs/common/src/auth/models/request/identity-token/token-two-factor.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TwoFactorProviderType } from "../../../enums/two-factor-provider-type"; export class TokenTwoFactorRequest { diff --git a/libs/common/src/auth/models/request/identity-token/token.request.ts b/libs/common/src/auth/models/request/identity-token/token.request.ts index 5560cb49dfd..390f184c069 100644 --- a/libs/common/src/auth/models/request/identity-token/token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/token.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DeviceRequest } from "./device.request"; import { TokenTwoFactorRequest } from "./token-two-factor.request"; diff --git a/libs/common/src/auth/models/request/organization-sso.request.ts b/libs/common/src/auth/models/request/organization-sso.request.ts index 68cbfd045a2..3b6b40fc4e7 100644 --- a/libs/common/src/auth/models/request/organization-sso.request.ts +++ b/libs/common/src/auth/models/request/organization-sso.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SsoConfigApi } from "../api/sso-config.api"; export class OrganizationSsoRequest { diff --git a/libs/common/src/auth/models/request/password.request.ts b/libs/common/src/auth/models/request/password.request.ts index 601492c98b3..674754ff41a 100644 --- a/libs/common/src/auth/models/request/password.request.ts +++ b/libs/common/src/auth/models/request/password.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class PasswordRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/registration/register-finish.request.ts b/libs/common/src/auth/models/request/registration/register-finish.request.ts index 7ffac6bfe6c..645bcf05b1b 100644 --- a/libs/common/src/auth/models/request/registration/register-finish.request.ts +++ b/libs/common/src/auth/models/request/registration/register-finish.request.ts @@ -1,5 +1,6 @@ +import { KdfType } from "@bitwarden/key-management"; + import { KeysRequest } from "../../../../models/request/keys.request"; -import { KdfType } from "../../../../platform/enums"; import { EncryptedString } from "../../../../platform/models/domain/enc-string"; export class RegisterFinishRequest { diff --git a/libs/common/src/auth/models/request/secret-verification.request.ts b/libs/common/src/auth/models/request/secret-verification.request.ts index 63427a67c2c..bb5d913656e 100644 --- a/libs/common/src/auth/models/request/secret-verification.request.ts +++ b/libs/common/src/auth/models/request/secret-verification.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SecretVerificationRequest { masterPasswordHash: string; otp: string; diff --git a/libs/common/src/auth/models/request/set-key-connector-key.request.ts b/libs/common/src/auth/models/request/set-key-connector-key.request.ts index c8081bdec2a..14132ab79f2 100644 --- a/libs/common/src/auth/models/request/set-key-connector-key.request.ts +++ b/libs/common/src/auth/models/request/set-key-connector-key.request.ts @@ -1,6 +1,6 @@ +import { KdfConfig, KdfType } from "@bitwarden/key-management"; + import { KeysRequest } from "../../../models/request/keys.request"; -import { KdfType } from "../../../platform/enums"; -import { KdfConfig } from "../domain/kdf-config"; export class SetKeyConnectorKeyRequest { key: string; diff --git a/libs/common/src/auth/models/request/set-password.request.ts b/libs/common/src/auth/models/request/set-password.request.ts index 0fc5d84c097..5aa74068591 100644 --- a/libs/common/src/auth/models/request/set-password.request.ts +++ b/libs/common/src/auth/models/request/set-password.request.ts @@ -1,5 +1,6 @@ +import { KdfType } from "@bitwarden/key-management"; + import { KeysRequest } from "../../../models/request/keys.request"; -import { KdfType } from "../../../platform/enums"; export class SetPasswordRequest { masterPasswordHash: string; diff --git a/libs/common/src/auth/models/request/two-factor-email.request.ts b/libs/common/src/auth/models/request/two-factor-email.request.ts index 769f6171c64..bdff27cd700 100644 --- a/libs/common/src/auth/models/request/two-factor-email.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class TwoFactorEmailRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/two-factor-provider.request.ts b/libs/common/src/auth/models/request/two-factor-provider.request.ts index 8d9718c25c6..05cec8c421f 100644 --- a/libs/common/src/auth/models/request/two-factor-provider.request.ts +++ b/libs/common/src/auth/models/request/two-factor-provider.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; import { SecretVerificationRequest } from "./secret-verification.request"; diff --git a/libs/common/src/auth/models/request/two-factor-recovery.request.ts b/libs/common/src/auth/models/request/two-factor-recovery.request.ts index 59a20752ab3..79ef6da280c 100644 --- a/libs/common/src/auth/models/request/two-factor-recovery.request.ts +++ b/libs/common/src/auth/models/request/two-factor-recovery.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class TwoFactorRecoveryRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/update-devices-trust.request.ts b/libs/common/src/auth/models/request/update-devices-trust.request.ts index 000e9f139da..21fe0f600dc 100644 --- a/libs/common/src/auth/models/request/update-devices-trust.request.ts +++ b/libs/common/src/auth/models/request/update-devices-trust.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateDevicesTrustRequest extends SecretVerificationRequest { @@ -6,8 +8,8 @@ export class UpdateDevicesTrustRequest extends SecretVerificationRequest { } export class DeviceKeysUpdateRequest { - encryptedPublicKey: string; - encryptedUserKey: string; + encryptedPublicKey: string | undefined; + encryptedUserKey: string | undefined; } export class OtherDeviceKeysUpdateRequest extends DeviceKeysUpdateRequest { diff --git a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts index cf6577cb76c..8c90fa379b4 100644 --- a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts +++ b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest { diff --git a/libs/common/src/auth/models/request/update-temp-password.request.ts b/libs/common/src/auth/models/request/update-temp-password.request.ts index 47e76d5e9d8..8f922f9008b 100644 --- a/libs/common/src/auth/models/request/update-temp-password.request.ts +++ b/libs/common/src/auth/models/request/update-temp-password.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTempPasswordRequest extends OrganizationUserResetPasswordRequest { diff --git a/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts index d39f55fe8c2..501802cb474 100644 --- a/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorAuthenticatorRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts b/libs/common/src/auth/models/request/update-two-factor-duo.request.ts index c67b1117412..9d9d2596450 100644 --- a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-duo.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorDuoRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/update-two-factor-email.request.ts b/libs/common/src/auth/models/request/update-two-factor-email.request.ts index 51a351f83a2..6baa0ed32fc 100644 --- a/libs/common/src/auth/models/request/update-two-factor-email.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-email.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorEmailRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts b/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts index 0d62b490650..73068c1e7dd 100644 --- a/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorWebAuthnDeleteRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts b/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts index 70d71322ebd..57b1655eac1 100644 --- a/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorWebAuthnRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts b/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts index b6e698f5b25..148e6c02fab 100644 --- a/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorYubikeyOtpRequest extends SecretVerificationRequest { diff --git a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts index c7f65850ffe..84103fe5d29 100644 --- a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts +++ b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts @@ -1,6 +1,10 @@ -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "../../../../../auth/src/common/models"; +import { EncString } from "../../../platform/models/domain/enc-string"; export class WebauthnRotateCredentialRequest { id: string; diff --git a/libs/common/src/auth/models/response/auth-request.response.ts b/libs/common/src/auth/models/response/auth-request.response.ts index e37b9013f86..d0c5d663065 100644 --- a/libs/common/src/auth/models/response/auth-request.response.ts +++ b/libs/common/src/auth/models/response/auth-request.response.ts @@ -8,8 +8,8 @@ export class AuthRequestResponse extends BaseResponse { publicKey: string; requestDeviceType: DeviceType; requestIpAddress: string; - key: string; - masterPasswordHash: string; + key: string; // could be either an encrypted MasterKey or an encrypted UserKey + masterPasswordHash: string; // if hash is present, the `key` above is an encrypted MasterKey (else `key` is an encrypted UserKey) creationDate: string; requestApproved?: boolean; responseDate?: string; diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index 9ddec9d0f70..5f128d340bf 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { KdfType } from "@bitwarden/key-management"; + import { BaseResponse } from "../../../models/response/base.response"; -import { KdfType } from "../../../platform/enums"; import { MasterPasswordPolicyResponse } from "./master-password-policy.response"; import { UserDecryptionOptionsResponse } from "./user-decryption-options/user-decryption-options.response"; diff --git a/libs/common/src/auth/models/response/organization-sso.response.ts b/libs/common/src/auth/models/response/organization-sso.response.ts index def1fccf08d..acc764c144e 100644 --- a/libs/common/src/auth/models/response/organization-sso.response.ts +++ b/libs/common/src/auth/models/response/organization-sso.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { SsoConfigApi } from "../api/sso-config.api"; diff --git a/libs/common/src/auth/models/response/prelogin.response.ts b/libs/common/src/auth/models/response/prelogin.response.ts index c6762e22376..b5ca78c3b79 100644 --- a/libs/common/src/auth/models/response/prelogin.response.ts +++ b/libs/common/src/auth/models/response/prelogin.response.ts @@ -1,5 +1,6 @@ +import { KdfType } from "@bitwarden/key-management"; + import { BaseResponse } from "../../../models/response/base.response"; -import { KdfType } from "../../../platform/enums"; export class PreloginResponse extends BaseResponse { kdf: KdfType; diff --git a/libs/common/src/auth/models/response/protected-device.response.ts b/libs/common/src/auth/models/response/protected-device.response.ts index 6d279f70c73..14662236197 100644 --- a/libs/common/src/auth/models/response/protected-device.response.ts +++ b/libs/common/src/auth/models/response/protected-device.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { DeviceType } from "../../../enums"; diff --git a/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts b/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts index 88e1f3a7168..1bee00ede1c 100644 --- a/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts +++ b/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; import { EncString } from "../../../../platform/models/domain/enc-string"; diff --git a/libs/common/src/auth/models/response/user-decryption-options/webauthn-prf-decryption-option.response.ts b/libs/common/src/auth/models/response/user-decryption-options/webauthn-prf-decryption-option.response.ts index 777b3dffc43..7988b08d56f 100644 --- a/libs/common/src/auth/models/response/user-decryption-options/webauthn-prf-decryption-option.response.ts +++ b/libs/common/src/auth/models/response/user-decryption-options/webauthn-prf-decryption-option.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; import { EncString } from "../../../../platform/models/domain/enc-string"; diff --git a/libs/common/src/auth/models/view/sso-config.view.ts b/libs/common/src/auth/models/view/sso-config.view.ts index 3524fa0ca62..2d8cabb077b 100644 --- a/libs/common/src/auth/models/view/sso-config.view.ts +++ b/libs/common/src/auth/models/view/sso-config.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../models/view/view"; import { MemberDecryptionType, diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index 3da04395fdc..d4479815c5d 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatestWith, map, diff --git a/libs/common/src/auth/services/anonymous-hub.service.ts b/libs/common/src/auth/services/anonymous-hub.service.ts index 747fbc39178..3900dd53ee0 100644 --- a/libs/common/src/auth/services/anonymous-hub.service.ts +++ b/libs/common/src/auth/services/anonymous-hub.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { HttpTransportType, HubConnection, @@ -7,6 +9,8 @@ import { import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; import { firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { NotificationType } from "../../enums"; import { diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index 5663384714d..4b7b8a2b262 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 2b8cd7919fd..d855a1be34f 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, combineLatest, @@ -9,6 +11,8 @@ import { switchMap, } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { StateService } from "../../platform/abstractions/state.service"; diff --git a/libs/common/src/auth/services/avatar.service.ts b/libs/common/src/auth/services/avatar.service.ts index 9b8c83968dd..8b9e7036485 100644 --- a/libs/common/src/auth/services/avatar.service.ts +++ b/libs/common/src/auth/services/avatar.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index 1738ab10bb6..a94c8b6e422 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -1,7 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, Observable } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { AppIdService } from "../../platform/abstractions/app-id.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; @@ -333,6 +337,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); return new SymmetricCryptoKey(userKey) as UserKey; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // If either decryption effort fails, we want to remove the device key this.logService.error("Failed to decrypt using device key. Removing device key."); diff --git a/libs/common/src/auth/services/device-trust.service.spec.ts b/libs/common/src/auth/services/device-trust.service.spec.ts index 66a91a693e5..943653e3129 100644 --- a/libs/common/src/auth/services/device-trust.service.spec.ts +++ b/libs/common/src/auth/services/device-trust.service.spec.ts @@ -3,7 +3,11 @@ import { BehaviorSubject, of } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptions } from "../../../../auth/src/common/models/domain/user-decryption-options"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeActiveUserState } from "../../../spec/fake-state"; diff --git a/libs/common/src/auth/services/devices-api.service.implementation.spec.ts b/libs/common/src/auth/services/devices-api.service.implementation.spec.ts new file mode 100644 index 00000000000..7aea36c7bd4 --- /dev/null +++ b/libs/common/src/auth/services/devices-api.service.implementation.spec.ts @@ -0,0 +1,100 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "../../abstractions/api.service"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; + +import { DevicesApiServiceImplementation } from "./devices-api.service.implementation"; + +describe("DevicesApiServiceImplementation", () => { + let devicesApiService: DevicesApiServiceImplementation; + let apiService: MockProxy; + + beforeEach(() => { + apiService = mock(); + devicesApiService = new DevicesApiServiceImplementation(apiService); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("getKnownDevice", () => { + it("calls api with correct parameters", async () => { + const email = "test@example.com"; + const deviceIdentifier = "device123"; + apiService.send.mockResolvedValue(true); + + const result = await devicesApiService.getKnownDevice(email, deviceIdentifier); + + expect(result).toBe(true); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + "/devices/knowndevice", + null, + false, + true, + null, + expect.any(Function), + ); + }); + }); + + describe("getDeviceByIdentifier", () => { + it("returns device response", async () => { + const deviceIdentifier = "device123"; + const mockResponse = { id: "123", name: "Test Device" }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await devicesApiService.getDeviceByIdentifier(deviceIdentifier); + + expect(result).toBeInstanceOf(DeviceResponse); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/devices/identifier/${deviceIdentifier}`, + null, + true, + true, + ); + }); + }); + + describe("updateTrustedDeviceKeys", () => { + it("updates device keys and returns device response", async () => { + const deviceIdentifier = "device123"; + const publicKeyEncrypted = "encryptedPublicKey"; + const userKeyEncrypted = "encryptedUserKey"; + const deviceKeyEncrypted = "encryptedDeviceKey"; + const mockResponse = { id: "123", name: "Test Device" }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await devicesApiService.updateTrustedDeviceKeys( + deviceIdentifier, + publicKeyEncrypted, + userKeyEncrypted, + deviceKeyEncrypted, + ); + + expect(result).toBeInstanceOf(DeviceResponse); + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + `/devices/${deviceIdentifier}/keys`, + { + encryptedPrivateKey: deviceKeyEncrypted, + encryptedPublicKey: userKeyEncrypted, + encryptedUserKey: publicKeyEncrypted, + }, + true, + true, + ); + }); + }); + + describe("error handling", () => { + it("propagates api errors", async () => { + const error = new Error("API Error"); + apiService.send.mockRejectedValue(error); + + await expect(devicesApiService.getDevices()).rejects.toThrow("API Error"); + }); + }); +}); diff --git a/libs/common/src/auth/services/devices-api.service.implementation.ts b/libs/common/src/auth/services/devices-api.service.implementation.ts index fbef5c782e4..cf760effbdf 100644 --- a/libs/common/src/auth/services/devices-api.service.implementation.ts +++ b/libs/common/src/auth/services/devices-api.service.implementation.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ApiService } from "../../abstractions/api.service"; import { ListResponse } from "../../models/response/list.response"; import { Utils } from "../../platform/misc/utils"; @@ -115,4 +117,8 @@ export class DevicesApiServiceImplementation implements DevicesApiServiceAbstrac }, ); } + + async deactivateDevice(deviceId: string): Promise { + await this.apiService.send("POST", `/devices/${deviceId}/deactivate`, null, true, false); + } } diff --git a/libs/common/src/auth/services/devices/devices.service.implementation.ts b/libs/common/src/auth/services/devices/devices.service.implementation.ts index 6032ed66a89..cdaa7a9fc4e 100644 --- a/libs/common/src/auth/services/devices/devices.service.implementation.ts +++ b/libs/common/src/auth/services/devices/devices.service.implementation.ts @@ -1,6 +1,7 @@ import { Observable, defer, map } from "rxjs"; import { ListResponse } from "../../../models/response/list.response"; +import { AppIdService } from "../../../platform/abstractions/app-id.service"; import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction"; import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { DeviceView } from "../../abstractions/devices/views/device.view"; @@ -15,7 +16,10 @@ import { DevicesApiServiceAbstraction } from "../../abstractions/devices-api.ser * (i.e., promsise --> observables are cold until subscribed to) */ export class DevicesServiceImplementation implements DevicesServiceAbstraction { - constructor(private devicesApiService: DevicesApiServiceAbstraction) {} + constructor( + private devicesApiService: DevicesApiServiceAbstraction, + private appIdService: AppIdService, + ) {} /** * @description Gets the list of all devices. @@ -65,4 +69,21 @@ export class DevicesServiceImplementation implements DevicesServiceAbstraction { ), ).pipe(map((deviceResponse: DeviceResponse) => new DeviceView(deviceResponse))); } + + /** + * @description Deactivates a device + */ + deactivateDevice$(deviceId: string): Observable { + return defer(() => this.devicesApiService.deactivateDevice(deviceId)); + } + + /** + * @description Gets the current device. + */ + getCurrentDevice$(): Observable { + return defer(async () => { + const deviceIdentifier = await this.appIdService.getAppId(); + return this.devicesApiService.getDeviceByIdentifier(deviceIdentifier); + }); + } } diff --git a/libs/common/src/auth/services/key-connector.service.spec.ts b/libs/common/src/auth/services/key-connector.service.spec.ts index b1bf87693c1..660f1124f4a 100644 --- a/libs/common/src/auth/services/key-connector.service.spec.ts +++ b/libs/common/src/auth/services/key-connector.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/auth/services/key-connector.service.ts b/libs/common/src/auth/services/key-connector.service.ts index 111f82e6e52..f798413162e 100644 --- a/libs/common/src/auth/services/key-connector.service.ts +++ b/libs/common/src/auth/services/key-connector.service.ts @@ -1,8 +1,16 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; +import { + Argon2KdfConfig, + KdfConfig, + PBKDF2KdfConfig, + KeyService, + KdfType, +} from "@bitwarden/key-management"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "../../admin-console/enums"; @@ -10,7 +18,6 @@ import { Organization } from "../../admin-console/models/domain/organization"; import { KeysRequest } from "../../models/request/keys.request"; import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; import { LogService } from "../../platform/abstractions/log.service"; -import { KdfType } from "../../platform/enums/kdf-type.enum"; import { Utils } from "../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { @@ -25,7 +32,6 @@ import { AccountService } from "../abstractions/account.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction"; import { TokenService } from "../abstractions/token.service"; -import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig } from "../models/domain/kdf-config"; import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request"; import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request"; import { IdentityTokenResponse } from "../models/response/identity-token.response"; diff --git a/libs/common/src/auth/services/master-password/fake-master-password.service.ts b/libs/common/src/auth/services/master-password/fake-master-password.service.ts index 0357018e615..2809659cb01 100644 --- a/libs/common/src/auth/services/master-password/fake-master-password.service.ts +++ b/libs/common/src/auth/services/master-password/fake-master-password.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { ReplaySubject, Observable } from "rxjs"; diff --git a/libs/common/src/auth/services/master-password/master-password.service.ts b/libs/common/src/auth/services/master-password/master-password.service.ts index 3a565e1c786..14e7522a836 100644 --- a/libs/common/src/auth/services/master-password/master-password.service.ts +++ b/libs/common/src/auth/services/master-password/master-password.service.ts @@ -1,9 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, Observable } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; import { StateService } from "../../../platform/abstractions/state.service"; import { EncryptionType } from "../../../platform/enums"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; @@ -178,10 +179,18 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr let decUserKey: Uint8Array; if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.encryptService.decryptToBytes(userKey, masterKey); + decUserKey = await this.encryptService.decryptToBytes( + userKey, + masterKey, + "Content: User Key; Encrypting Key: Master Key", + ); } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { const newKey = await this.keyGenerationService.stretchKey(masterKey); - decUserKey = await this.encryptService.decryptToBytes(userKey, newKey); + decUserKey = await this.encryptService.decryptToBytes( + userKey, + newKey, + "Content: User Key; Encrypting Key: Stretched Master Key", + ); } else { throw new Error("Unsupported encryption type."); } diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index ed622b21c86..ddd24ae7907 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -2,13 +2,15 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { UserId } from "../../../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; +import { UserId } from "../../types/guid"; import { Account, AccountInfo, AccountService } from "../abstractions/account.service"; import { PasswordResetEnrollmentServiceImplementation } from "./password-reset-enrollment.service.implementation"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index 9adcd0b7c17..22d5384e6ac 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -1,13 +1,17 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; import { UserKey } from "../../types/key"; diff --git a/libs/common/src/auth/services/sso-login.service.ts b/libs/common/src/auth/services/sso-login.service.ts index 3df6ef3540c..32019e8d568 100644 --- a/libs/common/src/auth/services/sso-login.service.ts +++ b/libs/common/src/auth/services/sso-login.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index c2150bc5c52..4b7cc2cab01 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, combineLatest, firstValueFrom, map } from "rxjs"; import { Opaque } from "type-fest"; diff --git a/libs/common/src/auth/services/two-factor.service.ts b/libs/common/src/auth/services/two-factor.service.ts index d67929c46f0..3826ffaaa22 100644 --- a/libs/common/src/auth/services/two-factor.service.ts +++ b/libs/common/src/auth/services/two-factor.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { I18nService } from "../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index 1538f571cfd..102e4bac8da 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -7,22 +7,26 @@ import { UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; - -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; +import { + BiometricsService, + BiometricsStatus, + KdfConfig, + KeyService, +} from "@bitwarden/key-management"; + +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { KdfConfigService } from "../../../../../key-management/src/abstractions/kdf-config.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; import { VaultTimeoutSettingsService } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { HashPurpose } from "../../../platform/enums"; import { Utils } from "../../../platform/misc/utils"; import { UserId } from "../../../types/guid"; import { MasterKey } from "../../../types/key"; -import { KdfConfigService } from "../../abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction"; import { VerificationType } from "../../enums/verification-type"; -import { KdfConfig } from "../../models/domain/kdf-config"; import { MasterPasswordPolicyResponse } from "../../models/response/master-password-policy.response"; import { MasterPasswordVerification } from "../../types/verification"; @@ -37,10 +41,9 @@ describe("UserVerificationService", () => { const userVerificationApiService = mock(); const userDecryptionOptionsService = mock(); const pinService = mock(); - const logService = mock(); const vaultTimeoutSettingsService = mock(); - const platformUtilsService = mock(); const kdfConfigService = mock(); + const biometricsService = mock(); const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; @@ -57,10 +60,8 @@ describe("UserVerificationService", () => { userVerificationApiService, userDecryptionOptionsService, pinService, - logService, - vaultTimeoutSettingsService, - platformUtilsService, kdfConfigService, + biometricsService, ); }); @@ -114,26 +115,15 @@ describe("UserVerificationService", () => { ); test.each([ - [true, true, true, true], - [true, true, true, false], - [true, true, false, false], - [false, true, false, true], - [false, false, false, false], - [false, false, true, false], - [false, false, false, true], + [true, BiometricsStatus.Available], + [false, BiometricsStatus.DesktopDisconnected], + [false, BiometricsStatus.HardwareUnavailable], ])( "returns %s for biometrics availability when isBiometricLockSet is %s, hasUserKeyStored is %s, and supportsSecureStorage is %s", - async ( - expectedReturn: boolean, - isBiometricsLockSet: boolean, - isBiometricsUserKeyStored: boolean, - platformSupportSecureStorage: boolean, - ) => { + async (expectedReturn: boolean, biometricsStatus: BiometricsStatus) => { setMasterPasswordAvailability(false); setPinAvailability("DISABLED"); - vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(isBiometricsLockSet); - keyService.hasUserKeyStored.mockResolvedValue(isBiometricsUserKeyStored); - platformUtilsService.supportsSecureStorage.mockReturnValue(platformSupportSecureStorage); + biometricsService.getBiometricsStatus.mockResolvedValue(biometricsStatus); const result = await sut.getAvailableVerificationOptions("client"); diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 5446558a540..4735da32b6b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -1,19 +1,22 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; - +import { + BiometricsService, + BiometricsStatus, + KdfConfigService, + KeyService, +} from "@bitwarden/key-management"; + +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { HashPurpose } from "../../../platform/enums"; -import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enum"; import { UserId } from "../../../types/guid"; -import { UserKey } from "../../../types/key"; import { AccountService } from "../../abstractions/account.service"; -import { KdfConfigService } from "../../abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction"; @@ -46,10 +49,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti private userVerificationApiService: UserVerificationApiServiceAbstraction, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private pinService: PinServiceAbstraction, - private logService: LogService, - private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction, - private platformUtilsService: PlatformUtilsService, private kdfConfigService: KdfConfigService, + private biometricsService: BiometricsService, ) {} async getAvailableVerificationOptions( @@ -57,17 +58,13 @@ export class UserVerificationService implements UserVerificationServiceAbstracti ): Promise { const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (verificationType === "client") { - const [ - userHasMasterPassword, - isPinDecryptionAvailable, - biometricsLockSet, - biometricsUserKeyStored, - ] = await Promise.all([ - this.hasMasterPasswordAndMasterKeyHash(userId), - this.pinService.isPinDecryptionAvailable(userId), - this.vaultTimeoutSettingsService.isBiometricLockSet(userId), - this.keyService.hasUserKeyStored(KeySuffixOptions.Biometric, userId), - ]); + const [userHasMasterPassword, isPinDecryptionAvailable, biometricsStatus] = await Promise.all( + [ + this.hasMasterPasswordAndMasterKeyHash(userId), + this.pinService.isPinDecryptionAvailable(userId), + this.biometricsService.getBiometricsStatus(), + ], + ); // note: we do not need to check this.platformUtilsService.supportsBiometric() because // we can just use the logic below which works for both desktop & the browser extension. @@ -76,9 +73,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti client: { masterPassword: userHasMasterPassword, pin: isPinDecryptionAvailable, - biometrics: - biometricsLockSet && - (biometricsUserKeyStored || !this.platformUtilsService.supportsSecureStorage()), + biometrics: biometricsStatus === BiometricsStatus.Available, }, server: { masterPassword: false, @@ -168,6 +163,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti const request = new VerifyOTPRequest(verification.secret); try { await this.userVerificationApiService.postAccountVerifyOTP(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidVerificationCode")); } @@ -226,6 +223,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti request.masterPasswordHash = serverKeyHash; try { policyOptions = await this.userVerificationApiService.postAccountVerifyPassword(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidMasterPassword")); } @@ -252,17 +251,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti } private async verifyUserByBiometrics(): Promise { - let userKey: UserKey; - // Biometrics crashes and doesn't return a value if the user cancels the prompt - try { - userKey = await this.keyService.getUserKeyFromStorage(KeySuffixOptions.Biometric); - } catch (e) { - this.logService.error(`Biometrics User Verification failed: ${e.message}`); - // So, any failures should be treated as a failed verification - return false; - } - - return userKey != null; + return this.biometricsService.authenticateWithBiometrics(); } async requestOTP() { diff --git a/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts b/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts index c524b958e39..746a0962e86 100644 --- a/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts +++ b/libs/common/src/auth/services/webauthn-login/request/webauthn-login-assertion-response.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Utils } from "../../../../platform/misc/utils"; diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts index 41f4994fab0..cea4bf29737 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common"; import { LogService } from "../../../platform/abstractions/log.service"; diff --git a/libs/common/src/auth/webauthn-iframe.ts b/libs/common/src/auth/webauthn-iframe.ts index 176f9c899d4..1e360a53507 100644 --- a/libs/common/src/auth/webauthn-iframe.ts +++ b/libs/common/src/auth/webauthn-iframe.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { I18nService } from "../platform/abstractions/i18n.service"; import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service"; diff --git a/libs/common/src/autofill/services/autofill-settings.service.ts b/libs/common/src/autofill/services/autofill-settings.service.ts index 09fdde8997b..869ae02c102 100644 --- a/libs/common/src/autofill/services/autofill-settings.service.ts +++ b/libs/common/src/autofill/services/autofill-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, Observable } from "rxjs"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/common/src/autofill/services/badge-settings.service.ts b/libs/common/src/autofill/services/badge-settings.service.ts index e2f62b38b3f..c95b010fd2b 100644 --- a/libs/common/src/autofill/services/badge-settings.service.ts +++ b/libs/common/src/autofill/services/badge-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, Observable } from "rxjs"; import { diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index 24e3763eb45..36f7d0eacec 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -1,6 +1,8 @@ +import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; @@ -8,6 +10,7 @@ import { DefaultDomainSettingsService, DomainSettingsService } from "./domain-se describe("DefaultDomainSettingsService", () => { let domainSettingsService: DomainSettingsService; + let configService: MockProxy; const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); @@ -19,10 +22,13 @@ describe("DefaultDomainSettingsService", () => { ]; beforeEach(() => { - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(false)); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); jest.spyOn(domainSettingsService, "getUrlEquivalentDomains"); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); + domainSettingsService.blockedInteractionsUris$ = of({}); }); describe("getUrlEquivalentDomains", () => { diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index 7f2e8c31508..b2833b9ee25 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -1,11 +1,15 @@ -import { map, Observable } from "rxjs"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { map, Observable, switchMap, of } from "rxjs"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { NeverDomains, EquivalentDomains, UriMatchStrategySetting, UriMatchStrategy, } from "../../models/domain/domain-service"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { DOMAIN_SETTINGS_DISK, @@ -21,10 +25,20 @@ const SHOW_FAVICONS = new KeyDefinition(DOMAIN_SETTINGS_DISK, "showFavicons", { deserializer: (value: boolean) => value ?? true, }); +// Domain exclusion list for notifications const NEVER_DOMAINS = new KeyDefinition(DOMAIN_SETTINGS_DISK, "neverDomains", { deserializer: (value: NeverDomains) => value ?? null, }); +// Domain exclusion list for content script injections +const BLOCKED_INTERACTIONS_URIS = new KeyDefinition( + DOMAIN_SETTINGS_DISK, + "blockedInteractionsUris", + { + deserializer: (value: NeverDomains) => value ?? {}, + }, +); + const EQUIVALENT_DOMAINS = new UserKeyDefinition(DOMAIN_SETTINGS_DISK, "equivalentDomains", { deserializer: (value: EquivalentDomains) => value ?? null, clearOn: ["logout"], @@ -39,15 +53,45 @@ const DEFAULT_URI_MATCH_STRATEGY = new UserKeyDefinition( }, ); +/** + * The Domain Settings service; provides client settings state for "active client view" URI concerns + */ export abstract class DomainSettingsService { + /** + * Indicates if the favicons for ciphers' URIs should be shown instead of a placeholder + */ showFavicons$: Observable; setShowFavicons: (newValue: boolean) => Promise; + + /** + * User-specified URIs for which the client notifications should not appear + */ neverDomains$: Observable; setNeverDomains: (newValue: NeverDomains) => Promise; + + /** + * User-specified URIs for which client content script injections should not occur, and the state + * of banner/notice visibility for those domains within the client + */ + blockedInteractionsUris$: Observable; + setBlockedInteractionsUris: (newValue: NeverDomains) => Promise; + + /** + * URIs which should be treated as equivalent to each other for various concerns (autofill, etc) + */ equivalentDomains$: Observable; setEquivalentDomains: (newValue: EquivalentDomains, userId: UserId) => Promise; + + /** + * User-specified default for URI-matching strategies (for example, when determining relevant + * ciphers for an active browser tab). Can be overridden by cipher-specific settings. + */ defaultUriMatchStrategy$: Observable; setDefaultUriMatchStrategy: (newValue: UriMatchStrategySetting) => Promise; + + /** + * Helper function for the common resolution of a given URL against equivalent domains + */ getUrlEquivalentDomains: (url: string) => Observable>; } @@ -58,19 +102,37 @@ export class DefaultDomainSettingsService implements DomainSettingsService { private neverDomainsState: GlobalState; readonly neverDomains$: Observable; + private blockedInteractionsUrisState: GlobalState; + readonly blockedInteractionsUris$: Observable; + private equivalentDomainsState: ActiveUserState; readonly equivalentDomains$: Observable; private defaultUriMatchStrategyState: ActiveUserState; readonly defaultUriMatchStrategy$: Observable; - constructor(private stateProvider: StateProvider) { + constructor( + private stateProvider: StateProvider, + private configService: ConfigService, + ) { this.showFaviconsState = this.stateProvider.getGlobal(SHOW_FAVICONS); this.showFavicons$ = this.showFaviconsState.state$.pipe(map((x) => x ?? true)); this.neverDomainsState = this.stateProvider.getGlobal(NEVER_DOMAINS); this.neverDomains$ = this.neverDomainsState.state$.pipe(map((x) => x ?? null)); + // Needs to be global to prevent pre-login injections + this.blockedInteractionsUrisState = this.stateProvider.getGlobal(BLOCKED_INTERACTIONS_URIS); + + this.blockedInteractionsUris$ = this.configService + .getFeatureFlag$(FeatureFlag.BlockBrowserInjectionsByDomain) + .pipe( + switchMap((featureIsEnabled) => + featureIsEnabled ? this.blockedInteractionsUrisState.state$ : of({} as NeverDomains), + ), + map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : {})), + ); + this.equivalentDomainsState = this.stateProvider.getActive(EQUIVALENT_DOMAINS); this.equivalentDomains$ = this.equivalentDomainsState.state$.pipe(map((x) => x ?? null)); @@ -88,6 +150,10 @@ export class DefaultDomainSettingsService implements DomainSettingsService { await this.neverDomainsState.update(() => newValue); } + async setBlockedInteractionsUris(newValue: NeverDomains): Promise { + await this.blockedInteractionsUrisState.update(() => newValue); + } + async setEquivalentDomains(newValue: EquivalentDomains, userId: UserId): Promise { await this.stateProvider.getUser(userId, EQUIVALENT_DOMAINS).update(() => newValue); } diff --git a/libs/common/src/autofill/services/user-notification-settings.service.ts b/libs/common/src/autofill/services/user-notification-settings.service.ts index 1a3a387df6b..149a07a029b 100644 --- a/libs/common/src/autofill/services/user-notification-settings.service.ts +++ b/libs/common/src/autofill/services/user-notification-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, Observable } from "rxjs"; import { diff --git a/libs/common/src/autofill/utils.spec.ts b/libs/common/src/autofill/utils.spec.ts index b09dc723b8e..554dc973b48 100644 --- a/libs/common/src/autofill/utils.spec.ts +++ b/libs/common/src/autofill/utils.spec.ts @@ -1,9 +1,6 @@ -import { - normalizeExpiryYearFormat, - isCardExpired, - parseYearMonthExpiry, -} from "@bitwarden/common/autofill/utils"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { CardView } from "../vault/models/view/card.view"; + +import { normalizeExpiryYearFormat, isCardExpired, parseYearMonthExpiry } from "./utils"; function getExpiryYearValueFormats(currentCentury: string) { return [ @@ -86,12 +83,14 @@ function getCardExpiryDateValues() { // `Date` months are zero-indexed, our expiry date month inputs are one-indexed const currentMonth = currentDate.getMonth() + 1; + const currentDateLastMonth = new Date(currentDate.setMonth(-1)); + return [ [null, null, false], // no month, no year [undefined, undefined, false], // no month, no year, invalid values ["", "", false], // no month, no year, invalid values ["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values - ["0", `${currentYear}`, true], // invalid month + ["0", `${currentYear}`, false], // invalid month ["0", `${currentYear - 1}`, true], // invalid 0 month ["00", `${currentYear + 1}`, false], // invalid 0 month [`${currentMonth}`, "0000", true], // current month, in the year 2000 @@ -103,7 +102,7 @@ function getCardExpiryDateValues() { [`${currentMonth + 36}`, `${currentYear - 1}`, true], // even though the month value would put the date 3 years into the future when calculated with `Date`, an explicit year in the past indicates the card is expired [`${currentMonth}`, `${currentYear}`, false], // this year, this month (not expired until the month is over) [`${currentMonth}`, `${currentYear}`.slice(-2), false], // This month, this year (not expired until the month is over) - [`${currentMonth - 1}`, `${currentYear}`, true], // last month + [`${currentDateLastMonth.getMonth() + 1}`, `${currentDateLastMonth.getFullYear()}`, true], // last month [`${currentMonth - 1}`, `${currentYear + 1}`, false], // 11 months from now ]; } diff --git a/libs/common/src/autofill/utils.ts b/libs/common/src/autofill/utils.ts index 0b127e4b9da..a77ea8a715d 100644 --- a/libs/common/src/autofill/utils.ts +++ b/libs/common/src/autofill/utils.ts @@ -1,11 +1,12 @@ +import { CardView } from "../vault/models/view/card.view"; + import { DelimiterPatternExpression, ExpiryFullYearPattern, ExpiryFullYearPatternExpression, IrrelevantExpiryCharactersPatternExpression, MonthPatternExpression, -} from "@bitwarden/common/autofill/constants"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +} from "./constants"; type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; @@ -23,11 +24,11 @@ export function normalizeExpiryYearFormat(yearInput: string | number): Year | nu let expirationYear = yearInputIsEmpty ? null : `${yearInput}`; // Exit early if year is already formatted correctly or empty - if (yearInputIsEmpty || /^[1-9]{1}\d{3}$/.test(expirationYear)) { + if (yearInputIsEmpty || (expirationYear && /^[1-9]{1}\d{3}$/.test(expirationYear))) { return expirationYear as Year; } - expirationYear = expirationYear + expirationYear = (expirationYear || "") // For safety, because even input[type="number"] will allow decimals .replace(/[^\d]/g, "") // remove any leading zero padding (leave the last leading zero if it ends the string) @@ -51,7 +52,7 @@ export function normalizeExpiryYearFormat(yearInput: string | number): Year | nu /** * Takes a cipher card view and returns "true" if the month and year affirmativey indicate - * the card is expired. + * the card is expired. Uncertain cases return "false". * * @param {CardView} cipherCard * @return {*} {boolean} @@ -60,27 +61,38 @@ export function isCardExpired(cipherCard: CardView): boolean { if (cipherCard) { const { expMonth = null, expYear = null } = cipherCard; + if (!expYear) { + return false; + } + const now = new Date(); const normalizedYear = normalizeExpiryYearFormat(expYear); + const parsedYear = normalizedYear ? parseInt(normalizedYear, 10) : NaN; - // If the card year is before the current year, don't bother checking the month - if (normalizedYear && parseInt(normalizedYear, 10) < now.getFullYear()) { + const expiryYearIsBeforeCurrentYear = parsedYear < now.getFullYear(); + const expiryYearIsAfterCurrentYear = parsedYear > now.getFullYear(); + + // If the expiry year is before the current year, skip checking the month, since it must be expired + if (normalizedYear && expiryYearIsBeforeCurrentYear) { return true; } + // If the expiry year is after the current year, skip checking the month, since it cannot be expired + if (normalizedYear && expiryYearIsAfterCurrentYear) { + return false; + } + if (normalizedYear && expMonth) { const parsedMonthInteger = parseInt(expMonth, 10); + const parsedMonthIsValid = parsedMonthInteger && !isNaN(parsedMonthInteger); - const parsedMonth = isNaN(parsedMonthInteger) - ? 0 - : // Add a month floor of 0 to protect against an invalid low month value of "0" or negative integers - Math.max( - // `Date` months are zero-indexed - parsedMonthInteger - 1, - 0, - ); + // If the parsed month value is 0, we don't know when the expiry passes this year, so do not treat it as expired + if (!parsedMonthIsValid) { + return false; + } - const parsedYear = parseInt(normalizedYear, 10); + // `Date` months are zero-indexed + const parsedMonth = parsedMonthInteger - 1; // First day of the next month const cardExpiry = new Date(parsedYear, parsedMonth + 1, 1); @@ -248,13 +260,18 @@ function parseNonDelimitedYearMonthExpiry(dateInput: string): [string | null, st parsedMonth = dateInput.slice(-1); const currentYear = new Date().getFullYear(); - const normalizedParsedYear = parseInt(normalizeExpiryYearFormat(parsedYear), 10); - const normalizedParsedYearAlternative = parseInt( - normalizeExpiryYearFormat(dateInput.slice(-2)), - 10, - ); - - if (normalizedParsedYear < currentYear && normalizedParsedYearAlternative >= currentYear) { + const normalizedYearFormat = normalizeExpiryYearFormat(parsedYear); + const normalizedParsedYear = normalizedYearFormat && parseInt(normalizedYearFormat, 10); + const normalizedExpiryYearFormat = normalizeExpiryYearFormat(dateInput.slice(-2)); + const normalizedParsedYearAlternative = + normalizedExpiryYearFormat && parseInt(normalizedExpiryYearFormat, 10); + + if ( + normalizedParsedYear && + normalizedParsedYear < currentYear && + normalizedParsedYearAlternative && + normalizedParsedYearAlternative >= currentYear + ) { parsedYear = dateInput.slice(-2); parsedMonth = dateInput.slice(0, 1); } @@ -286,17 +303,24 @@ export function parseYearMonthExpiry(combinedExpiryValue: string): [Year | null, // If there is only one date part, no delimiter was found in the passed value if (dateParts.length === 1) { - [parsedYear, parsedMonth] = parseNonDelimitedYearMonthExpiry(sanitizedFirstPart); + const [parsedNonDelimitedYear, parsedNonDelimitedMonth] = + parseNonDelimitedYearMonthExpiry(sanitizedFirstPart); + + parsedYear = parsedNonDelimitedYear; + parsedMonth = parsedNonDelimitedMonth; } // There are multiple date parts else { - [parsedYear, parsedMonth] = parseDelimitedYearMonthExpiry([ + const [parsedDelimitedYear, parsedDelimitedMonth] = parseDelimitedYearMonthExpiry([ sanitizedFirstPart, sanitizedSecondPart, ]); + + parsedYear = parsedDelimitedYear; + parsedMonth = parsedDelimitedMonth; } - const normalizedParsedYear = normalizeExpiryYearFormat(parsedYear); + const normalizedParsedYear = parsedYear ? normalizeExpiryYearFormat(parsedYear) : null; const normalizedParsedMonth = parsedMonth?.replace(/^0+/, "").slice(0, 2); // Set "empty" values to null diff --git a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts index 66f164ce50e..e0e8b7377c5 100644 --- a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts @@ -1,7 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class AccountBillingApiServiceAbstraction { getBillingInvoices: (status?: string, startAfter?: string) => Promise; diff --git a/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts b/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts index 080c61e9ffb..a4253226880 100644 --- a/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserId } from "../../../types/guid"; @@ -9,27 +11,32 @@ export type BillingAccountProfile = { export abstract class BillingAccountProfileStateService { /** - * Emits `true` when the active user's account has been granted premium from any of the + * Emits `true` when the user's account has been granted premium from any of the * organizations it is a member of. Otherwise, emits `false` */ - hasPremiumFromAnyOrganization$: Observable; + abstract hasPremiumFromAnyOrganization$(userId: UserId): Observable; /** - * Emits `true` when the active user's account has an active premium subscription at the + * Emits `true` when the user's account has an active premium subscription at the * individual user level */ - hasPremiumPersonally$: Observable; + abstract hasPremiumPersonally$(userId: UserId): Observable; /** * Emits `true` when either `hasPremiumPersonally` or `hasPremiumFromAnyOrganization` is `true` */ - hasPremiumFromAnySource$: Observable; + abstract hasPremiumFromAnySource$(userId: UserId): Observable; /** - * Sets the active user's premium status fields upon every full sync, either from their personal + * Emits `true` when the subscription menu item should be shown in navigation. + * This is hidden for organizations that provide premium, except if the user has premium personally + * or has a billing history. + */ + abstract canViewSubscription$(userId: UserId): Observable; + + /** + * Sets the user's premium status fields upon every full sync, either from their personal * subscription to premium, or an organization they're a part of that grants them premium. - * @param hasPremiumPersonally - * @param hasPremiumFromAnyOrganization */ abstract setHasPremium( hasPremiumPersonally: boolean, diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index ef148d1aef4..928f65a3636 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -1,17 +1,20 @@ -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { @@ -72,4 +75,9 @@ export abstract class BillingApiServiceAbstraction { organizationId: string, request: VerifyBankAccountRequest, ) => Promise; + + restartSubscription: ( + organizationId: string, + request: OrganizationCreateRequest, + ) => Promise; } diff --git a/libs/common/src/billing/abstractions/index.ts b/libs/common/src/billing/abstractions/index.ts index 7ed0f1251af..3f72cd9d2c0 100644 --- a/libs/common/src/billing/abstractions/index.ts +++ b/libs/common/src/billing/abstractions/index.ts @@ -1,4 +1,3 @@ export * from "./account/billing-account-profile-state.service"; export * from "./billing-api.service.abstraction"; export * from "./organization-billing.service"; -export * from "./provider-billing.service.abstraction"; diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 72902baa30e..1e68488ac98 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -1,6 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore + import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; import { InitiationPath } from "../../models/request/reference-event.request"; import { PaymentMethodType, PlanType } from "../enums"; +import { BillingSourceResponse } from "../models/response/billing.response"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; export type OrganizationInformation = { name: string; @@ -41,11 +46,20 @@ export type SubscriptionInformation = { }; export abstract class OrganizationBillingServiceAbstraction { - purchaseSubscription: (subscription: SubscriptionInformation) => Promise; + getPaymentSource: ( + organizationId: string, + ) => Promise; - startFree: (subscription: SubscriptionInformation) => Promise; + purchaseSubscription: (subscription: SubscriptionInformation) => Promise; purchaseSubscriptionNoPaymentMethod: ( subscription: SubscriptionInformation, ) => Promise; + + startFree: (subscription: SubscriptionInformation) => Promise; + + restartSubscription: ( + organizationId: string, + subscription: SubscriptionInformation, + ) => Promise; } diff --git a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts index 639f1fdb7c7..2ed25491049 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts @@ -1,7 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class OrganizationBillingApiServiceAbstraction { getBillingInvoices: ( diff --git a/libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts b/libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts deleted file mode 100644 index aa8568d8e9c..00000000000 --- a/libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { map, Observable, OperatorFunction, switchMap } from "rxjs"; - -import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; -import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -type MaybeProvider = Provider | undefined; - -export const hasConsolidatedBilling = ( - configService: ConfigService, -): OperatorFunction => - switchMap>((provider) => - configService - .getFeatureFlag$(FeatureFlag.EnableConsolidatedBilling) - .pipe( - map((consolidatedBillingEnabled) => - provider - ? provider.providerStatus === ProviderStatusType.Billable && consolidatedBillingEnabled - : false, - ), - ), - ); diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts new file mode 100644 index 00000000000..7a744dae856 --- /dev/null +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -0,0 +1,18 @@ +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; + +export abstract class TaxServiceAbstraction { + abstract getCountries(): CountryListItem[]; + + abstract isCountrySupported(country: string): Promise; + + abstract previewIndividualInvoice( + request: PreviewIndividualInvoiceRequest, + ): Promise; + + abstract previewOrganizationInvoice( + request: PreviewOrganizationInvoiceRequest, + ): Promise; +} diff --git a/libs/common/src/billing/models/api/billing-sync-config.api.ts b/libs/common/src/billing/models/api/billing-sync-config.api.ts index bb3b8969a39..518cc2cb0c0 100644 --- a/libs/common/src/billing/models/api/billing-sync-config.api.ts +++ b/libs/common/src/billing/models/api/billing-sync-config.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class BillingSyncConfigApi extends BaseResponse { diff --git a/libs/common/src/billing/models/domain/country-list-item.ts b/libs/common/src/billing/models/domain/country-list-item.ts new file mode 100644 index 00000000000..79abb96871c --- /dev/null +++ b/libs/common/src/billing/models/domain/country-list-item.ts @@ -0,0 +1,5 @@ +export type CountryListItem = { + name: string; + value: string; + disabled: boolean; +}; diff --git a/libs/common/src/billing/models/domain/index.ts b/libs/common/src/billing/models/domain/index.ts index 0f53c3e116c..057f6dc4e84 100644 --- a/libs/common/src/billing/models/domain/index.ts +++ b/libs/common/src/billing/models/domain/index.ts @@ -1,2 +1,3 @@ export * from "./bank-account"; +export * from "./country-list-item"; export * from "./tax-information"; diff --git a/libs/common/src/billing/models/domain/tax-information.ts b/libs/common/src/billing/models/domain/tax-information.ts index 81cbc6e8e4d..794cdef3ed4 100644 --- a/libs/common/src/billing/models/domain/tax-information.ts +++ b/libs/common/src/billing/models/domain/tax-information.ts @@ -1,4 +1,6 @@ -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { TaxInfoResponse } from "../response/tax-info.response"; export class TaxInformation { country: string; diff --git a/libs/common/src/billing/models/request/bit-pay-invoice.request.ts b/libs/common/src/billing/models/request/bit-pay-invoice.request.ts index 51fb07fd47f..bd82228c732 100644 --- a/libs/common/src/billing/models/request/bit-pay-invoice.request.ts +++ b/libs/common/src/billing/models/request/bit-pay-invoice.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class BitPayInvoiceRequest { userId: string; organizationId: string; diff --git a/libs/common/src/billing/models/request/create-client-organization.request.ts b/libs/common/src/billing/models/request/create-client-organization.request.ts index 2eac23531af..c7078164993 100644 --- a/libs/common/src/billing/models/request/create-client-organization.request.ts +++ b/libs/common/src/billing/models/request/create-client-organization.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request"; import { PlanType } from "../../../billing/enums"; diff --git a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts index f06795c0805..83b254ac512 100644 --- a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts +++ b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts @@ -1,4 +1,6 @@ -import { TaxInformation } from "@bitwarden/common/billing/models/domain/tax-information"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { TaxInformation } from "../domain/tax-information"; import { TaxInfoUpdateRequest } from "./tax-info-update.request"; @@ -10,6 +12,10 @@ export class ExpandedTaxInfoUpdateRequest extends TaxInfoUpdateRequest { state: string; static From(taxInformation: TaxInformation): ExpandedTaxInfoUpdateRequest { + if (!taxInformation) { + return null; + } + const request = new ExpandedTaxInfoUpdateRequest(); request.country = taxInformation.country; request.postalCode = taxInformation.postalCode; diff --git a/libs/common/src/billing/models/request/organization-no-payment-method-create-request.ts b/libs/common/src/billing/models/request/organization-no-payment-method-create-request.ts index b48caec8dfc..cfa9981d0db 100644 --- a/libs/common/src/billing/models/request/organization-no-payment-method-create-request.ts +++ b/libs/common/src/billing/models/request/organization-no-payment-method-create-request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request"; import { InitiationPath } from "../../../models/request/reference-event.request"; import { PlanType } from "../../enums"; diff --git a/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts b/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts index 7971b1f6a91..2cfd2405e71 100644 --- a/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts +++ b/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class OrganizationSmSubscriptionUpdateRequest { /** * The number of seats to add or remove from the subscription. diff --git a/libs/common/src/billing/models/request/payment.request.ts b/libs/common/src/billing/models/request/payment.request.ts index e73a10bcea7..e2edd9aabb3 100644 --- a/libs/common/src/billing/models/request/payment.request.ts +++ b/libs/common/src/billing/models/request/payment.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PaymentMethodType } from "../../enums"; import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; diff --git a/libs/common/src/billing/models/request/preview-individual-invoice.request.ts b/libs/common/src/billing/models/request/preview-individual-invoice.request.ts new file mode 100644 index 00000000000..f817398c629 --- /dev/null +++ b/libs/common/src/billing/models/request/preview-individual-invoice.request.ts @@ -0,0 +1,28 @@ +// @ts-strict-ignore +export class PreviewIndividualInvoiceRequest { + passwordManager: PasswordManager; + taxInformation: TaxInformation; + + constructor(passwordManager: PasswordManager, taxInformation: TaxInformation) { + this.passwordManager = passwordManager; + this.taxInformation = taxInformation; + } +} + +class PasswordManager { + additionalStorage: number; + + constructor(additionalStorage: number) { + this.additionalStorage = additionalStorage; + } +} + +class TaxInformation { + postalCode: string; + country: string; + + constructor(postalCode: string, country: string) { + this.postalCode = postalCode; + this.country = country; + } +} diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts new file mode 100644 index 00000000000..40d8db03d3b --- /dev/null +++ b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts @@ -0,0 +1,54 @@ +import { PlanType } from "../../enums"; + +export class PreviewOrganizationInvoiceRequest { + organizationId?: string; + passwordManager: PasswordManager; + secretsManager?: SecretsManager; + taxInformation: TaxInformation; + + constructor( + passwordManager: PasswordManager, + taxInformation: TaxInformation, + organizationId?: string, + secretsManager?: SecretsManager, + ) { + this.organizationId = organizationId; + this.passwordManager = passwordManager; + this.secretsManager = secretsManager; + this.taxInformation = taxInformation; + } +} + +class PasswordManager { + plan: PlanType; + seats: number; + additionalStorage: number; + + constructor(plan: PlanType, seats: number, additionalStorage: number) { + this.plan = plan; + this.seats = seats; + this.additionalStorage = additionalStorage; + } +} + +class SecretsManager { + seats: number; + additionalMachineAccounts: number; + + constructor(seats: number, additionalMachineAccounts: number) { + this.seats = seats; + this.additionalMachineAccounts = additionalMachineAccounts; + } +} + +class TaxInformation { + postalCode: string; + country: string; + taxId: string; + + constructor(postalCode: string, country: string, taxId: string) { + this.postalCode = postalCode; + this.country = country; + this.taxId = taxId; + } +} diff --git a/libs/common/src/billing/models/request/sm-subscribe.request.ts b/libs/common/src/billing/models/request/sm-subscribe.request.ts index 581d3007c81..e322f989984 100644 --- a/libs/common/src/billing/models/request/sm-subscribe.request.ts +++ b/libs/common/src/billing/models/request/sm-subscribe.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SecretsManagerSubscribeRequest { additionalSmSeats: number; additionalServiceAccounts: number; diff --git a/libs/common/src/billing/models/request/tax-info-update.request.ts b/libs/common/src/billing/models/request/tax-info-update.request.ts index 6881a8a432c..6f767535472 100644 --- a/libs/common/src/billing/models/request/tax-info-update.request.ts +++ b/libs/common/src/billing/models/request/tax-info-update.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class TaxInfoUpdateRequest { country: string; postalCode: string; diff --git a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts index 26faa036d57..e4bf575cc6a 100644 --- a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts +++ b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts @@ -1,4 +1,6 @@ -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { PaymentMethodType } from "../../enums"; export class TokenizedPaymentSourceRequest { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/request/update-client-organization.request.ts b/libs/common/src/billing/models/request/update-client-organization.request.ts index 5cd43a2dd76..e21344b3d6b 100644 --- a/libs/common/src/billing/models/request/update-client-organization.request.ts +++ b/libs/common/src/billing/models/request/update-client-organization.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class UpdateClientOrganizationRequest { assignedSeats: number; name: string; diff --git a/libs/common/src/billing/models/request/update-payment-method.request.ts b/libs/common/src/billing/models/request/update-payment-method.request.ts index 613dc636482..10b03103716 100644 --- a/libs/common/src/billing/models/request/update-payment-method.request.ts +++ b/libs/common/src/billing/models/request/update-payment-method.request.ts @@ -1,5 +1,7 @@ -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; +import { TokenizedPaymentSourceRequest } from "./tokenized-payment-source.request"; export class UpdatePaymentMethodRequest { paymentSource: TokenizedPaymentSourceRequest; diff --git a/libs/common/src/billing/models/request/verify-bank-account.request.ts b/libs/common/src/billing/models/request/verify-bank-account.request.ts index cadf4b97099..ee85d1a2aad 100644 --- a/libs/common/src/billing/models/request/verify-bank-account.request.ts +++ b/libs/common/src/billing/models/request/verify-bank-account.request.ts @@ -1,9 +1,7 @@ export class VerifyBankAccountRequest { - amount1: number; - amount2: number; + descriptorCode: string; - constructor(amount1: number, amount2: number) { - this.amount1 = amount1; - this.amount2 = amount2; + constructor(descriptorCode: string) { + this.descriptorCode = descriptorCode; } } diff --git a/libs/common/src/billing/models/response/billing-payment.response.ts b/libs/common/src/billing/models/response/billing-payment.response.ts index 534df4d7c78..e60a11c0772 100644 --- a/libs/common/src/billing/models/response/billing-payment.response.ts +++ b/libs/common/src/billing/models/response/billing-payment.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { BillingSourceResponse } from "./billing.response"; diff --git a/libs/common/src/billing/models/response/billing.response.ts b/libs/common/src/billing/models/response/billing.response.ts index b94fc1b64b0..4bb874a8989 100644 --- a/libs/common/src/billing/models/response/billing.response.ts +++ b/libs/common/src/billing/models/response/billing.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { PaymentMethodType, TransactionType } from "../../enums"; diff --git a/libs/common/src/billing/models/response/invoices.response.ts b/libs/common/src/billing/models/response/invoices.response.ts index bf797ba42d6..05c95b83c6f 100644 --- a/libs/common/src/billing/models/response/invoices.response.ts +++ b/libs/common/src/billing/models/response/invoices.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class InvoicesResponse extends BaseResponse { invoices: InvoiceResponse[] = []; diff --git a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts index ae6d1ac92c1..d30ad76a147 100644 --- a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts +++ b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts @@ -5,6 +5,12 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { isManaged: boolean; isOnSecretsManagerStandalone: boolean; isSubscriptionUnpaid: boolean; + hasSubscription: boolean; + hasOpenInvoice: boolean; + invoiceDueDate: Date | null; + invoiceCreatedDate: Date | null; + subPeriodEndDate: Date | null; + isSubscriptionCanceled: boolean; constructor(response: any) { super(response); @@ -12,5 +18,16 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { this.isManaged = this.getResponseProperty("IsManaged"); this.isOnSecretsManagerStandalone = this.getResponseProperty("IsOnSecretsManagerStandalone"); this.isSubscriptionUnpaid = this.getResponseProperty("IsSubscriptionUnpaid"); + this.hasSubscription = this.getResponseProperty("HasSubscription"); + this.hasOpenInvoice = this.getResponseProperty("HasOpenInvoice"); + + this.invoiceDueDate = this.parseDate(this.getResponseProperty("InvoiceDueDate")); + this.invoiceCreatedDate = this.parseDate(this.getResponseProperty("InvoiceCreatedDate")); + this.subPeriodEndDate = this.parseDate(this.getResponseProperty("SubPeriodEndDate")); + this.isSubscriptionCanceled = this.getResponseProperty("IsSubscriptionCanceled"); + } + + private parseDate(dateString: any): Date | null { + return dateString ? new Date(dateString) : null; } } diff --git a/libs/common/src/billing/models/response/organization-subscription.response.ts b/libs/common/src/billing/models/response/organization-subscription.response.ts index 101eacef6dc..6e56eda68c6 100644 --- a/libs/common/src/billing/models/response/organization-subscription.response.ts +++ b/libs/common/src/billing/models/response/organization-subscription.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { OrganizationResponse } from "../../../admin-console/models/response/organization.response"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/billing/models/response/payment-source.response.ts b/libs/common/src/billing/models/response/payment-source.response.ts index 1aeeb450b11..93418fc2f55 100644 --- a/libs/common/src/billing/models/response/payment-source.response.ts +++ b/libs/common/src/billing/models/response/payment-source.response.ts @@ -1,5 +1,5 @@ -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; +import { PaymentMethodType } from "../../enums"; export class PaymentSourceResponse extends BaseResponse { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/response/payment.response.ts b/libs/common/src/billing/models/response/payment.response.ts index 87338e6389d..c0459a857af 100644 --- a/libs/common/src/billing/models/response/payment.response.ts +++ b/libs/common/src/billing/models/response/payment.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { ProfileResponse } from "../../../models/response/profile.response"; diff --git a/libs/common/src/billing/models/response/plan.response.ts b/libs/common/src/billing/models/response/plan.response.ts index 3d8ab283027..e10fff8545d 100644 --- a/libs/common/src/billing/models/response/plan.response.ts +++ b/libs/common/src/billing/models/response/plan.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ProductTierType, PlanType } from "../../../billing/enums"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/billing/models/response/preview-invoice.response.ts b/libs/common/src/billing/models/response/preview-invoice.response.ts new file mode 100644 index 00000000000..efd3da3e9f1 --- /dev/null +++ b/libs/common/src/billing/models/response/preview-invoice.response.ts @@ -0,0 +1,16 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class PreviewInvoiceResponse extends BaseResponse { + effectiveTaxRate: number; + taxableBaseAmount: number; + taxAmount: number; + totalAmount: number; + + constructor(response: any) { + super(response); + this.effectiveTaxRate = this.getResponseProperty("EffectiveTaxRate"); + this.taxableBaseAmount = this.getResponseProperty("TaxableBaseAmount"); + this.taxAmount = this.getResponseProperty("TaxAmount"); + this.totalAmount = this.getResponseProperty("TotalAmount"); + } +} diff --git a/libs/common/src/billing/models/response/provider-subscription-response.ts b/libs/common/src/billing/models/response/provider-subscription-response.ts index 2ecf988addd..4481f7588ff 100644 --- a/libs/common/src/billing/models/response/provider-subscription-response.ts +++ b/libs/common/src/billing/models/response/provider-subscription-response.ts @@ -1,9 +1,9 @@ -import { ProviderType } from "@bitwarden/common/admin-console/enums"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { SubscriptionSuspensionResponse } from "@bitwarden/common/billing/models/response/subscription-suspension.response"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; - +import { ProviderType } from "../../../admin-console/enums"; import { BaseResponse } from "../../../models/response/base.response"; +import { PlanType, ProductTierType } from "../../enums"; + +import { SubscriptionSuspensionResponse } from "./subscription-suspension.response"; +import { TaxInfoResponse } from "./tax-info.response"; export class ProviderSubscriptionResponse extends BaseResponse { status: string; diff --git a/libs/common/src/billing/models/response/subscription-suspension.response.ts b/libs/common/src/billing/models/response/subscription-suspension.response.ts index 418e1c443c8..3d714a05dba 100644 --- a/libs/common/src/billing/models/response/subscription-suspension.response.ts +++ b/libs/common/src/billing/models/response/subscription-suspension.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class SubscriptionSuspensionResponse extends BaseResponse { suspensionDate: string; diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts index a05a40624d3..3bc7d42651c 100644 --- a/libs/common/src/billing/models/response/subscription.response.ts +++ b/libs/common/src/billing/models/response/subscription.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class SubscriptionResponse extends BaseResponse { diff --git a/libs/common/src/billing/models/response/tax-id-types.response.ts b/libs/common/src/billing/models/response/tax-id-types.response.ts new file mode 100644 index 00000000000..f31f2133b34 --- /dev/null +++ b/libs/common/src/billing/models/response/tax-id-types.response.ts @@ -0,0 +1,28 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class TaxIdTypesResponse extends BaseResponse { + taxIdTypes: TaxIdTypeResponse[] = []; + + constructor(response: any) { + super(response); + const taxIdTypes = this.getResponseProperty("TaxIdTypes"); + if (taxIdTypes && taxIdTypes.length) { + this.taxIdTypes = taxIdTypes.map((t: any) => new TaxIdTypeResponse(t)); + } + } +} + +export class TaxIdTypeResponse extends BaseResponse { + code: string; + country: string; + description: string; + example: string; + + constructor(response: any) { + super(response); + this.code = this.getResponseProperty("Code"); + this.country = this.getResponseProperty("Country"); + this.description = this.getResponseProperty("Description"); + this.example = this.getResponseProperty("Example"); + } +} diff --git a/libs/common/src/billing/models/view/self-hosted-organization-subscription.view.ts b/libs/common/src/billing/models/view/self-hosted-organization-subscription.view.ts index 7b496882948..f15bb2cd5d8 100644 --- a/libs/common/src/billing/models/view/self-hosted-organization-subscription.view.ts +++ b/libs/common/src/billing/models/view/self-hosted-organization-subscription.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../models/view/view"; import { OrganizationSubscriptionResponse } from "../response/organization-subscription.response"; diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts index 7e0dee0eedf..02dbef469d6 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts @@ -6,8 +6,11 @@ import { FakeStateProvider, FakeSingleUserState, } from "../../../../spec"; +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { UserId } from "../../../types/guid"; import { BillingAccountProfile } from "../../abstractions/account/billing-account-profile-state.service"; +import { BillingHistoryResponse } from "../../models/response/billing-history.response"; import { BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, @@ -19,14 +22,26 @@ describe("BillingAccountProfileStateService", () => { let sut: DefaultBillingAccountProfileStateService; let userBillingAccountProfileState: FakeSingleUserState; let accountService: FakeAccountService; + let platformUtilsService: jest.Mocked; + let apiService: jest.Mocked; const userId = "fakeUserId" as UserId; beforeEach(() => { accountService = mockAccountServiceWith(userId); stateProvider = new FakeStateProvider(accountService); - - sut = new DefaultBillingAccountProfileStateService(stateProvider); + platformUtilsService = { + isSelfHost: jest.fn(), + } as any; + apiService = { + getUserBillingHistory: jest.fn(), + } as any; + + sut = new DefaultBillingAccountProfileStateService( + stateProvider, + platformUtilsService, + apiService, + ); userBillingAccountProfileState = stateProvider.singleUser.getFake( userId, @@ -45,7 +60,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: true, }); - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$(userId))).toBe(true); }); it("return false when they do not have premium from an organization", async () => { @@ -54,13 +69,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(false); - }); - - it("returns false when there is no active user", async () => { - await accountService.switchAccount(null); - - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(false); + expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$(userId))).toBe(false); }); }); @@ -71,7 +80,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumPersonally$(userId))).toBe(true); }); it("returns false when the user does not have premium personally", async () => { @@ -80,13 +89,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(false); - }); - - it("returns false when there is no active user", async () => { - await accountService.switchAccount(null); - - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(false); + expect(await firstValueFrom(sut.hasPremiumPersonally$(userId))).toBe(false); }); }); @@ -97,7 +100,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); it("returns true when the user has premium from an organization", async () => { @@ -106,7 +109,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: true, }); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); it("returns true when they have premium personally AND from an organization", async () => { @@ -115,23 +118,87 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: true, }); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); + }); - it("returns false when there is no active user", async () => { - await accountService.switchAccount(null); + describe("setHasPremium", () => { + it("should update the user's state when called", async () => { + await sut.setHasPremium(true, false, userId); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(false); + expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$(userId))).toBe(false); + expect(await firstValueFrom(sut.hasPremiumPersonally$(userId))).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); }); - describe("setHasPremium", () => { - it("should update the active users state when called", async () => { - await sut.setHasPremium(true, false, userId); + describe("canViewSubscription$", () => { + beforeEach(() => { + platformUtilsService.isSelfHost.mockReturnValue(false); + apiService.getUserBillingHistory.mockResolvedValue( + new BillingHistoryResponse({ invoices: [], transactions: [] }), + ); + }); + + it("returns true when user has premium personally", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: true, + hasPremiumFromAnyOrganization: true, + }); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(true); + }); + + it("returns true when user has no premium from any source", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: false, + }); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(true); + }); + + it("returns true when user has billing history in cloud environment", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: true, + }); + platformUtilsService.isSelfHost.mockReturnValue(false); + apiService.getUserBillingHistory.mockResolvedValue( + new BillingHistoryResponse({ + invoices: [{ id: "1" }], + transactions: [{ id: "2" }], + }), + ); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(true); + }); + + it("returns false when user has no premium personally, has org premium, and no billing history", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: true, + }); + platformUtilsService.isSelfHost.mockReturnValue(false); + apiService.getUserBillingHistory.mockResolvedValue( + new BillingHistoryResponse({ + invoices: [], + transactions: [], + }), + ); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(false); + }); + + it("returns false when user has no premium personally, has org premium, in self-hosted environment", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: true, + }); + platformUtilsService.isSelfHost.mockReturnValue(true); - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(false); - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(true); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(false); + expect(apiService.getUserBillingHistory).not.toHaveBeenCalled(); }); }); }); diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts index 7d256da9714..155ce1493b4 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts @@ -1,11 +1,8 @@ -import { map, Observable, of, switchMap } from "rxjs"; +import { map, Observable, combineLatest, concatMap } from "rxjs"; -import { - ActiveUserState, - BILLING_DISK, - StateProvider, - UserKeyDefinition, -} from "../../../platform/state"; +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { BILLING_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { BillingAccountProfile, @@ -22,42 +19,34 @@ export const BILLING_ACCOUNT_PROFILE_KEY_DEFINITION = new UserKeyDefinition; - - hasPremiumFromAnyOrganization$: Observable; - hasPremiumPersonally$: Observable; - hasPremiumFromAnySource$: Observable; - - constructor(private readonly stateProvider: StateProvider) { - this.billingAccountProfileState = stateProvider.getActive( - BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, - ); + constructor( + private readonly stateProvider: StateProvider, + private readonly platformUtilsService: PlatformUtilsService, + private readonly apiService: ApiService, + ) {} - // Setup an observable that will always track the currently active user - // but will fallback to emitting null when there is no active user. - const billingAccountProfileOrNull = stateProvider.activeUserId$.pipe( - switchMap((userId) => - userId != null - ? stateProvider.getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION).state$ - : of(null), - ), - ); - - this.hasPremiumFromAnyOrganization$ = billingAccountProfileOrNull.pipe( - map((billingAccountProfile) => !!billingAccountProfile?.hasPremiumFromAnyOrganization), - ); + hasPremiumFromAnyOrganization$(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION) + .state$.pipe(map((profile) => !!profile?.hasPremiumFromAnyOrganization)); + } - this.hasPremiumPersonally$ = billingAccountProfileOrNull.pipe( - map((billingAccountProfile) => !!billingAccountProfile?.hasPremiumPersonally), - ); + hasPremiumPersonally$(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION) + .state$.pipe(map((profile) => !!profile?.hasPremiumPersonally)); + } - this.hasPremiumFromAnySource$ = billingAccountProfileOrNull.pipe( - map( - (billingAccountProfile) => - billingAccountProfile?.hasPremiumFromAnyOrganization === true || - billingAccountProfile?.hasPremiumPersonally === true, - ), - ); + hasPremiumFromAnySource$(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION) + .state$.pipe( + map( + (profile) => + profile?.hasPremiumFromAnyOrganization === true || + profile?.hasPremiumPersonally === true, + ), + ); } async setHasPremium( @@ -72,4 +61,23 @@ export class DefaultBillingAccountProfileStateService implements BillingAccountP }; }); } + + canViewSubscription$(userId: UserId): Observable { + return combineLatest([ + this.hasPremiumPersonally$(userId), + this.hasPremiumFromAnyOrganization$(userId), + ]).pipe( + concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => { + const isCloud = !this.platformUtilsService.isSelfHost(); + + let billing = null; + if (isCloud) { + billing = await this.apiService.getUserBillingHistory(); + } + + const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory; + return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory; + }), + ); + } } diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 534b22362d6..4306324395e 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -1,22 +1,25 @@ -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ToastService } from "@bitwarden/components"; import { ApiService } from "../../abstractions/api.service"; -import { BillingApiServiceAbstraction } from "../../billing/abstractions"; -import { PaymentMethodType } from "../../billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request"; -import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; -import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; -import { PlanResponse } from "../../billing/models/response/plan.response"; +import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; +import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; +import { LogService } from "../../platform/abstractions/log.service"; +import { BillingApiServiceAbstraction } from "../abstractions"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; +import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; +import { PlanResponse } from "../models/response/plan.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export class BillingApiService implements BillingApiServiceAbstraction { @@ -212,6 +215,19 @@ export class BillingApiService implements BillingApiServiceAbstraction { ); } + async restartSubscription( + organizationId: string, + request: OrganizationCreateRequest, + ): Promise { + return await this.apiService.send( + "POST", + "/organizations/" + organizationId + "/billing/restart-subscription", + request, + true, + false, + ); + } + private async execute(request: () => Promise): Promise { try { return await request(); diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index efc36278532..da1a1666ff0 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -1,23 +1,31 @@ -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { KeyService } from "@bitwarden/key-management"; + import { ApiService } from "../../abstractions/api.service"; import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { SyncService } from "../../platform/sync"; import { OrgKey } from "../../types/key"; -import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction"; import { + BillingApiServiceAbstraction, OrganizationBillingServiceAbstraction, OrganizationInformation, PaymentInformation, PlanInformation, SubscriptionInformation, -} from "../abstractions/organization-billing.service"; +} from "../abstractions"; import { PlanType } from "../enums"; import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request"; +import { BillingSourceResponse } from "../models/response/billing.response"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; interface OrganizationKeys { encryptedKey: EncString; @@ -29,6 +37,8 @@ interface OrganizationKeys { export class OrganizationBillingService implements OrganizationBillingServiceAbstraction { constructor( private apiService: ApiService, + private billingApiService: BillingApiServiceAbstraction, + private configService: ConfigService, private keyService: KeyService, private encryptService: EncryptService, private i18nService: I18nService, @@ -36,6 +46,23 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs private syncService: SyncService, ) {} + async getPaymentSource( + organizationId: string, + ): Promise { + const deprecateStripeSourcesAPI = await this.configService.getFeatureFlag( + FeatureFlag.AC2476_DeprecateStripeSourcesAPI, + ); + + if (deprecateStripeSourcesAPI) { + const paymentMethod = + await this.billingApiService.getOrganizationPaymentMethod(organizationId); + return paymentMethod.paymentSource; + } else { + const billing = await this.organizationApiService.getBilling(organizationId); + return billing.paymentSource; + } + } + async purchaseSubscription(subscription: SubscriptionInformation): Promise { const request = new OrganizationCreateRequest(); @@ -58,8 +85,10 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs return response; } - async startFree(subscription: SubscriptionInformation): Promise { - const request = new OrganizationCreateRequest(); + async purchaseSubscriptionNoPaymentMethod( + subscription: SubscriptionInformation, + ): Promise { + const request = new OrganizationNoPaymentMethodCreateRequest(); const organizationKeys = await this.makeOrganizationKeys(); @@ -69,7 +98,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs this.setPlanInformation(request, subscription.plan); - const response = await this.organizationApiService.create(request); + const response = await this.organizationApiService.createWithoutPayment(request); await this.apiService.refreshIdentityToken(); @@ -78,10 +107,8 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs return response; } - async purchaseSubscriptionNoPaymentMethod( - subscription: SubscriptionInformation, - ): Promise { - const request = new OrganizationNoPaymentMethodCreateRequest(); + async startFree(subscription: SubscriptionInformation): Promise { + const request = new OrganizationCreateRequest(); const organizationKeys = await this.makeOrganizationKeys(); @@ -91,7 +118,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs this.setPlanInformation(request, subscription.plan); - const response = await this.organizationApiService.createWithoutPayment(request); + const response = await this.organizationApiService.create(request); await this.apiService.refreshIdentityToken(); @@ -196,4 +223,17 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs request.additionalStorageGb = information.storage; } } + + async restartSubscription( + organizationId: string, + subscription: SubscriptionInformation, + ): Promise { + const request = new OrganizationCreateRequest(); + const organizationKeys = await this.makeOrganizationKeys(); + this.setOrganizationKeys(request, organizationKeys); + this.setOrganizationInformation(request, subscription.organization); + this.setPlanInformation(request, subscription.plan); + this.setPaymentInformation(request, subscription.payment); + await this.billingApiService.restartSubscription(organizationId, request); + } } diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts new file mode 100644 index 00000000000..aa27c99adc8 --- /dev/null +++ b/libs/common/src/billing/services/tax.service.ts @@ -0,0 +1,303 @@ +import { ApiService } from "../../abstractions/api.service"; +import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; + +export class TaxService implements TaxServiceAbstraction { + constructor(private apiService: ApiService) {} + + getCountries(): CountryListItem[] { + return [ + { name: "-- Select --", value: "", disabled: false }, + { name: "United States", value: "US", disabled: false }, + { name: "China", value: "CN", disabled: false }, + { name: "France", value: "FR", disabled: false }, + { name: "Germany", value: "DE", disabled: false }, + { name: "Canada", value: "CA", disabled: false }, + { name: "United Kingdom", value: "GB", disabled: false }, + { name: "Australia", value: "AU", disabled: false }, + { name: "India", value: "IN", disabled: false }, + { name: "", value: "-", disabled: true }, + { name: "Afghanistan", value: "AF", disabled: false }, + { name: "Åland Islands", value: "AX", disabled: false }, + { name: "Albania", value: "AL", disabled: false }, + { name: "Algeria", value: "DZ", disabled: false }, + { name: "American Samoa", value: "AS", disabled: false }, + { name: "Andorra", value: "AD", disabled: false }, + { name: "Angola", value: "AO", disabled: false }, + { name: "Anguilla", value: "AI", disabled: false }, + { name: "Antarctica", value: "AQ", disabled: false }, + { name: "Antigua and Barbuda", value: "AG", disabled: false }, + { name: "Argentina", value: "AR", disabled: false }, + { name: "Armenia", value: "AM", disabled: false }, + { name: "Aruba", value: "AW", disabled: false }, + { name: "Austria", value: "AT", disabled: false }, + { name: "Azerbaijan", value: "AZ", disabled: false }, + { name: "Bahamas", value: "BS", disabled: false }, + { name: "Bahrain", value: "BH", disabled: false }, + { name: "Bangladesh", value: "BD", disabled: false }, + { name: "Barbados", value: "BB", disabled: false }, + { name: "Belarus", value: "BY", disabled: false }, + { name: "Belgium", value: "BE", disabled: false }, + { name: "Belize", value: "BZ", disabled: false }, + { name: "Benin", value: "BJ", disabled: false }, + { name: "Bermuda", value: "BM", disabled: false }, + { name: "Bhutan", value: "BT", disabled: false }, + { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, + { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, + { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, + { name: "Botswana", value: "BW", disabled: false }, + { name: "Bouvet Island", value: "BV", disabled: false }, + { name: "Brazil", value: "BR", disabled: false }, + { name: "British Indian Ocean Territory", value: "IO", disabled: false }, + { name: "Brunei Darussalam", value: "BN", disabled: false }, + { name: "Bulgaria", value: "BG", disabled: false }, + { name: "Burkina Faso", value: "BF", disabled: false }, + { name: "Burundi", value: "BI", disabled: false }, + { name: "Cambodia", value: "KH", disabled: false }, + { name: "Cameroon", value: "CM", disabled: false }, + { name: "Cape Verde", value: "CV", disabled: false }, + { name: "Cayman Islands", value: "KY", disabled: false }, + { name: "Central African Republic", value: "CF", disabled: false }, + { name: "Chad", value: "TD", disabled: false }, + { name: "Chile", value: "CL", disabled: false }, + { name: "Christmas Island", value: "CX", disabled: false }, + { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, + { name: "Colombia", value: "CO", disabled: false }, + { name: "Comoros", value: "KM", disabled: false }, + { name: "Congo", value: "CG", disabled: false }, + { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, + { name: "Cook Islands", value: "CK", disabled: false }, + { name: "Costa Rica", value: "CR", disabled: false }, + { name: "Côte d'Ivoire", value: "CI", disabled: false }, + { name: "Croatia", value: "HR", disabled: false }, + { name: "Cuba", value: "CU", disabled: false }, + { name: "Curaçao", value: "CW", disabled: false }, + { name: "Cyprus", value: "CY", disabled: false }, + { name: "Czech Republic", value: "CZ", disabled: false }, + { name: "Denmark", value: "DK", disabled: false }, + { name: "Djibouti", value: "DJ", disabled: false }, + { name: "Dominica", value: "DM", disabled: false }, + { name: "Dominican Republic", value: "DO", disabled: false }, + { name: "Ecuador", value: "EC", disabled: false }, + { name: "Egypt", value: "EG", disabled: false }, + { name: "El Salvador", value: "SV", disabled: false }, + { name: "Equatorial Guinea", value: "GQ", disabled: false }, + { name: "Eritrea", value: "ER", disabled: false }, + { name: "Estonia", value: "EE", disabled: false }, + { name: "Ethiopia", value: "ET", disabled: false }, + { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, + { name: "Faroe Islands", value: "FO", disabled: false }, + { name: "Fiji", value: "FJ", disabled: false }, + { name: "Finland", value: "FI", disabled: false }, + { name: "French Guiana", value: "GF", disabled: false }, + { name: "French Polynesia", value: "PF", disabled: false }, + { name: "French Southern Territories", value: "TF", disabled: false }, + { name: "Gabon", value: "GA", disabled: false }, + { name: "Gambia", value: "GM", disabled: false }, + { name: "Georgia", value: "GE", disabled: false }, + { name: "Ghana", value: "GH", disabled: false }, + { name: "Gibraltar", value: "GI", disabled: false }, + { name: "Greece", value: "GR", disabled: false }, + { name: "Greenland", value: "GL", disabled: false }, + { name: "Grenada", value: "GD", disabled: false }, + { name: "Guadeloupe", value: "GP", disabled: false }, + { name: "Guam", value: "GU", disabled: false }, + { name: "Guatemala", value: "GT", disabled: false }, + { name: "Guernsey", value: "GG", disabled: false }, + { name: "Guinea", value: "GN", disabled: false }, + { name: "Guinea-Bissau", value: "GW", disabled: false }, + { name: "Guyana", value: "GY", disabled: false }, + { name: "Haiti", value: "HT", disabled: false }, + { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, + { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, + { name: "Honduras", value: "HN", disabled: false }, + { name: "Hong Kong", value: "HK", disabled: false }, + { name: "Hungary", value: "HU", disabled: false }, + { name: "Iceland", value: "IS", disabled: false }, + { name: "Indonesia", value: "ID", disabled: false }, + { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, + { name: "Iraq", value: "IQ", disabled: false }, + { name: "Ireland", value: "IE", disabled: false }, + { name: "Isle of Man", value: "IM", disabled: false }, + { name: "Israel", value: "IL", disabled: false }, + { name: "Italy", value: "IT", disabled: false }, + { name: "Jamaica", value: "JM", disabled: false }, + { name: "Japan", value: "JP", disabled: false }, + { name: "Jersey", value: "JE", disabled: false }, + { name: "Jordan", value: "JO", disabled: false }, + { name: "Kazakhstan", value: "KZ", disabled: false }, + { name: "Kenya", value: "KE", disabled: false }, + { name: "Kiribati", value: "KI", disabled: false }, + { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, + { name: "Korea, Republic of", value: "KR", disabled: false }, + { name: "Kuwait", value: "KW", disabled: false }, + { name: "Kyrgyzstan", value: "KG", disabled: false }, + { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, + { name: "Latvia", value: "LV", disabled: false }, + { name: "Lebanon", value: "LB", disabled: false }, + { name: "Lesotho", value: "LS", disabled: false }, + { name: "Liberia", value: "LR", disabled: false }, + { name: "Libya", value: "LY", disabled: false }, + { name: "Liechtenstein", value: "LI", disabled: false }, + { name: "Lithuania", value: "LT", disabled: false }, + { name: "Luxembourg", value: "LU", disabled: false }, + { name: "Macao", value: "MO", disabled: false }, + { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, + { name: "Madagascar", value: "MG", disabled: false }, + { name: "Malawi", value: "MW", disabled: false }, + { name: "Malaysia", value: "MY", disabled: false }, + { name: "Maldives", value: "MV", disabled: false }, + { name: "Mali", value: "ML", disabled: false }, + { name: "Malta", value: "MT", disabled: false }, + { name: "Marshall Islands", value: "MH", disabled: false }, + { name: "Martinique", value: "MQ", disabled: false }, + { name: "Mauritania", value: "MR", disabled: false }, + { name: "Mauritius", value: "MU", disabled: false }, + { name: "Mayotte", value: "YT", disabled: false }, + { name: "Mexico", value: "MX", disabled: false }, + { name: "Micronesia, Federated States of", value: "FM", disabled: false }, + { name: "Moldova, Republic of", value: "MD", disabled: false }, + { name: "Monaco", value: "MC", disabled: false }, + { name: "Mongolia", value: "MN", disabled: false }, + { name: "Montenegro", value: "ME", disabled: false }, + { name: "Montserrat", value: "MS", disabled: false }, + { name: "Morocco", value: "MA", disabled: false }, + { name: "Mozambique", value: "MZ", disabled: false }, + { name: "Myanmar", value: "MM", disabled: false }, + { name: "Namibia", value: "NA", disabled: false }, + { name: "Nauru", value: "NR", disabled: false }, + { name: "Nepal", value: "NP", disabled: false }, + { name: "Netherlands", value: "NL", disabled: false }, + { name: "New Caledonia", value: "NC", disabled: false }, + { name: "New Zealand", value: "NZ", disabled: false }, + { name: "Nicaragua", value: "NI", disabled: false }, + { name: "Niger", value: "NE", disabled: false }, + { name: "Nigeria", value: "NG", disabled: false }, + { name: "Niue", value: "NU", disabled: false }, + { name: "Norfolk Island", value: "NF", disabled: false }, + { name: "Northern Mariana Islands", value: "MP", disabled: false }, + { name: "Norway", value: "NO", disabled: false }, + { name: "Oman", value: "OM", disabled: false }, + { name: "Pakistan", value: "PK", disabled: false }, + { name: "Palau", value: "PW", disabled: false }, + { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, + { name: "Panama", value: "PA", disabled: false }, + { name: "Papua New Guinea", value: "PG", disabled: false }, + { name: "Paraguay", value: "PY", disabled: false }, + { name: "Peru", value: "PE", disabled: false }, + { name: "Philippines", value: "PH", disabled: false }, + { name: "Pitcairn", value: "PN", disabled: false }, + { name: "Poland", value: "PL", disabled: false }, + { name: "Portugal", value: "PT", disabled: false }, + { name: "Puerto Rico", value: "PR", disabled: false }, + { name: "Qatar", value: "QA", disabled: false }, + { name: "Réunion", value: "RE", disabled: false }, + { name: "Romania", value: "RO", disabled: false }, + { name: "Russian Federation", value: "RU", disabled: false }, + { name: "Rwanda", value: "RW", disabled: false }, + { name: "Saint Barthélemy", value: "BL", disabled: false }, + { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, + { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, + { name: "Saint Lucia", value: "LC", disabled: false }, + { name: "Saint Martin (French part)", value: "MF", disabled: false }, + { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, + { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, + { name: "Samoa", value: "WS", disabled: false }, + { name: "San Marino", value: "SM", disabled: false }, + { name: "Sao Tome and Principe", value: "ST", disabled: false }, + { name: "Saudi Arabia", value: "SA", disabled: false }, + { name: "Senegal", value: "SN", disabled: false }, + { name: "Serbia", value: "RS", disabled: false }, + { name: "Seychelles", value: "SC", disabled: false }, + { name: "Sierra Leone", value: "SL", disabled: false }, + { name: "Singapore", value: "SG", disabled: false }, + { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, + { name: "Slovakia", value: "SK", disabled: false }, + { name: "Slovenia", value: "SI", disabled: false }, + { name: "Solomon Islands", value: "SB", disabled: false }, + { name: "Somalia", value: "SO", disabled: false }, + { name: "South Africa", value: "ZA", disabled: false }, + { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, + { name: "South Sudan", value: "SS", disabled: false }, + { name: "Spain", value: "ES", disabled: false }, + { name: "Sri Lanka", value: "LK", disabled: false }, + { name: "Sudan", value: "SD", disabled: false }, + { name: "Suriname", value: "SR", disabled: false }, + { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, + { name: "Swaziland", value: "SZ", disabled: false }, + { name: "Sweden", value: "SE", disabled: false }, + { name: "Switzerland", value: "CH", disabled: false }, + { name: "Syrian Arab Republic", value: "SY", disabled: false }, + { name: "Taiwan", value: "TW", disabled: false }, + { name: "Tajikistan", value: "TJ", disabled: false }, + { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, + { name: "Thailand", value: "TH", disabled: false }, + { name: "Timor-Leste", value: "TL", disabled: false }, + { name: "Togo", value: "TG", disabled: false }, + { name: "Tokelau", value: "TK", disabled: false }, + { name: "Tonga", value: "TO", disabled: false }, + { name: "Trinidad and Tobago", value: "TT", disabled: false }, + { name: "Tunisia", value: "TN", disabled: false }, + { name: "Turkey", value: "TR", disabled: false }, + { name: "Turkmenistan", value: "TM", disabled: false }, + { name: "Turks and Caicos Islands", value: "TC", disabled: false }, + { name: "Tuvalu", value: "TV", disabled: false }, + { name: "Uganda", value: "UG", disabled: false }, + { name: "Ukraine", value: "UA", disabled: false }, + { name: "United Arab Emirates", value: "AE", disabled: false }, + { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, + { name: "Uruguay", value: "UY", disabled: false }, + { name: "Uzbekistan", value: "UZ", disabled: false }, + { name: "Vanuatu", value: "VU", disabled: false }, + { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, + { name: "Viet Nam", value: "VN", disabled: false }, + { name: "Virgin Islands, British", value: "VG", disabled: false }, + { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, + { name: "Wallis and Futuna", value: "WF", disabled: false }, + { name: "Western Sahara", value: "EH", disabled: false }, + { name: "Yemen", value: "YE", disabled: false }, + { name: "Zambia", value: "ZM", disabled: false }, + { name: "Zimbabwe", value: "ZW", disabled: false }, + ]; + } + + async isCountrySupported(country: string): Promise { + const response = await this.apiService.send( + "GET", + "/tax/is-country-supported?country=" + country, + null, + true, + true, + ); + return response; + } + + async previewIndividualInvoice( + request: PreviewIndividualInvoiceRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + "/accounts/billing/preview-invoice", + request, + true, + true, + ); + return new PreviewInvoiceResponse(response); + } + + async previewOrganizationInvoice( + request: PreviewOrganizationInvoiceRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + `/invoices/preview-organization`, + request, + true, + true, + ); + return new PreviewInvoiceResponse(response); + } +} diff --git a/libs/common/src/enums/device-type.enum.ts b/libs/common/src/enums/device-type.enum.ts index 1b8574a4c49..ff6329b9ac4 100644 --- a/libs/common/src/enums/device-type.enum.ts +++ b/libs/common/src/enums/device-type.enum.ts @@ -27,18 +27,40 @@ export enum DeviceType { LinuxCLI = 25, } -export const MobileDeviceTypes: Set = new Set([ - DeviceType.Android, - DeviceType.iOS, - DeviceType.AndroidAmazon, -]); +/** + * Device type metadata + * Each device type has a category corresponding to the client type and platform (Android, iOS, Chrome, Firefox, etc.) + */ +interface DeviceTypeMetadata { + category: "mobile" | "extension" | "webVault" | "desktop" | "cli" | "sdk" | "server"; + platform: string; +} -export const DesktopDeviceTypes: Set = new Set([ - DeviceType.WindowsDesktop, - DeviceType.MacOsDesktop, - DeviceType.LinuxDesktop, - DeviceType.UWP, - DeviceType.WindowsCLI, - DeviceType.MacOsCLI, - DeviceType.LinuxCLI, -]); +export const DeviceTypeMetadata: Record = { + [DeviceType.Android]: { category: "mobile", platform: "Android" }, + [DeviceType.iOS]: { category: "mobile", platform: "iOS" }, + [DeviceType.AndroidAmazon]: { category: "mobile", platform: "Amazon" }, + [DeviceType.ChromeExtension]: { category: "extension", platform: "Chrome" }, + [DeviceType.FirefoxExtension]: { category: "extension", platform: "Firefox" }, + [DeviceType.OperaExtension]: { category: "extension", platform: "Opera" }, + [DeviceType.EdgeExtension]: { category: "extension", platform: "Edge" }, + [DeviceType.VivaldiExtension]: { category: "extension", platform: "Vivaldi" }, + [DeviceType.SafariExtension]: { category: "extension", platform: "Safari" }, + [DeviceType.ChromeBrowser]: { category: "webVault", platform: "Chrome" }, + [DeviceType.FirefoxBrowser]: { category: "webVault", platform: "Firefox" }, + [DeviceType.OperaBrowser]: { category: "webVault", platform: "Opera" }, + [DeviceType.EdgeBrowser]: { category: "webVault", platform: "Edge" }, + [DeviceType.IEBrowser]: { category: "webVault", platform: "IE" }, + [DeviceType.SafariBrowser]: { category: "webVault", platform: "Safari" }, + [DeviceType.VivaldiBrowser]: { category: "webVault", platform: "Vivaldi" }, + [DeviceType.UnknownBrowser]: { category: "webVault", platform: "Unknown" }, + [DeviceType.WindowsDesktop]: { category: "desktop", platform: "Windows" }, + [DeviceType.MacOsDesktop]: { category: "desktop", platform: "macOS" }, + [DeviceType.LinuxDesktop]: { category: "desktop", platform: "Linux" }, + [DeviceType.UWP]: { category: "desktop", platform: "Windows UWP" }, + [DeviceType.WindowsCLI]: { category: "cli", platform: "Windows" }, + [DeviceType.MacOsCLI]: { category: "cli", platform: "macOS" }, + [DeviceType.LinuxCLI]: { category: "cli", platform: "Linux" }, + [DeviceType.SDK]: { category: "sdk", platform: "" }, + [DeviceType.Server]: { category: "server", platform: "" }, +}; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index d36aea241d5..feffe2ca442 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -4,42 +4,49 @@ * Flags MUST be short lived and SHALL be removed once enabled. */ export enum FeatureFlag { + /* Autofill */ + BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", + DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", + EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", + GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", + InlineMenuFieldQualification = "inline-menu-field-qualification", + InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", + InlineMenuTotp = "inline-menu-totp", + NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", + BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", GeneratorToolsModernization = "generator-tools-modernization", - EnableConsolidatedBilling = "enable-consolidated-billing", AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", ExtensionRefresh = "extension-refresh", PersistPopupView = "persist-popup-view", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", - UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", EmailVerification = "email-verification", - InlineMenuFieldQualification = "inline-menu-field-qualification", - MemberAccessReport = "ac-2059-member-access-report", TwoFactorComponentRefactor = "two-factor-component-refactor", - InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", IdpAutoSubmitLogin = "idp-auto-submit-login", UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", - EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub", - GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", - EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", AccountDeprovisioning = "pm-10308-account-deprovisioning", SSHKeyVaultItem = "ssh-key-vault-item", SSHAgent = "ssh-agent", - NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader", - AccessIntelligence = "pm-13227-access-intelligence", - Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions", - LimitCollectionCreationDeletionSplit = "pm-10863-limit-collection-creation-deletion-split", + PM14505AdminConsoleIntegrationPage = "pm-14505-admin-console-integration-page", CriticalApps = "pm-14466-risk-insights-critical-application", TrialPaymentOptional = "PM-8163-trial-payment", SecurityTasks = "security-tasks", + NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", + NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", + DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", + MacOsNativeCredentialSync = "macos-native-credential-sync", + PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", + PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", + PrivateKeyRegeneration = "pm-12241-private-key-regeneration", + ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -54,42 +61,49 @@ const FALSE = false as boolean; * We support true as a value as we prefer flags to "enable" not "disable". */ export const DefaultFeatureFlagValue = { + /* Autofill */ + [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, + [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, + [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, + [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, + [FeatureFlag.InlineMenuFieldQualification]: FALSE, + [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, + [FeatureFlag.InlineMenuTotp]: FALSE, + [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, + [FeatureFlag.BrowserFilelessImport]: FALSE, [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.GeneratorToolsModernization]: FALSE, - [FeatureFlag.EnableConsolidatedBilling]: FALSE, [FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE, [FeatureFlag.ExtensionRefresh]: FALSE, [FeatureFlag.PersistPopupView]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, - [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.EmailVerification]: FALSE, - [FeatureFlag.InlineMenuFieldQualification]: FALSE, - [FeatureFlag.MemberAccessReport]: FALSE, [FeatureFlag.TwoFactorComponentRefactor]: FALSE, - [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, - [FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE, - [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, - [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.AccountDeprovisioning]: FALSE, [FeatureFlag.SSHKeyVaultItem]: FALSE, [FeatureFlag.SSHAgent]: FALSE, - [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, [FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE, - [FeatureFlag.AccessIntelligence]: FALSE, - [FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE, - [FeatureFlag.LimitCollectionCreationDeletionSplit]: FALSE, + [FeatureFlag.PM14505AdminConsoleIntegrationPage]: FALSE, [FeatureFlag.CriticalApps]: FALSE, [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, + [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, + [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, + [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, + [FeatureFlag.MacOsNativeCredentialSync]: FALSE, + [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, + [FeatureFlag.PM12443RemovePagingLogic]: FALSE, + [FeatureFlag.PrivateKeyRegeneration]: FALSE, + [FeatureFlag.ResellerManagedOrgAlert]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; diff --git a/libs/common/src/enums/integration-type.enum.ts b/libs/common/src/enums/integration-type.enum.ts index acb95106976..42c385fe715 100644 --- a/libs/common/src/enums/integration-type.enum.ts +++ b/libs/common/src/enums/integration-type.enum.ts @@ -1,4 +1,9 @@ export enum IntegrationType { Integration = "integration", SDK = "sdk", + SSO = "sso", + SCIM = "scim", + BWDC = "bwdc", + EVENT = "event", + DEVICE = "device", } diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts index c5853cbe2c0..db59fcafa69 100644 --- a/libs/common/src/enums/notification-type.enum.ts +++ b/libs/common/src/enums/notification-type.enum.ts @@ -22,4 +22,6 @@ export enum NotificationType { AuthRequestResponse = 16, SyncOrganizations = 17, + SyncOrganizationStatusChanged = 18, + SyncOrganizationCollectionSettingChanged = 19, } diff --git a/libs/common/src/key-management/services/process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts similarity index 81% rename from libs/common/src/key-management/services/process-reload.service.ts rename to libs/common/src/key-management/services/default-process-reload.service.ts index 2f25d63b0fd..9f97e0a94c1 100644 --- a/libs/common/src/key-management/services/process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -1,18 +1,23 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; +import { MessagingService } from "../../platform/abstractions/messaging.service"; import { UserId } from "../../types/guid"; import { ProcessReloadServiceAbstraction } from "../abstractions/process-reload.service"; -export class ProcessReloadService implements ProcessReloadServiceAbstraction { +export class DefaultProcessReloadService implements ProcessReloadServiceAbstraction { private reloadInterval: any = null; constructor( @@ -22,6 +27,7 @@ export class ProcessReloadService implements ProcessReloadServiceAbstraction { private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private biometricStateService: BiometricStateService, private accountService: AccountService, + private logService: LogService, ) {} async startProcessReload(authService: AuthService): Promise { @@ -33,6 +39,9 @@ export class ProcessReloadService implements ProcessReloadServiceAbstraction { let status = await firstValueFrom(authService.authStatusFor$(userId as UserId)); status = await authService.getAuthStatus(userId); if (status === AuthenticationStatus.Unlocked) { + this.logService.info( + "[Process Reload Service] User unlocked, preventing process reload", + ); return; } } @@ -49,6 +58,9 @@ export class ProcessReloadService implements ProcessReloadServiceAbstraction { if (userId != null) { const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); if (ephemeralPin != null) { + this.logService.info( + "[Process Reload Service] Ephemeral pin active, preventing process reload", + ); return; } } @@ -91,7 +103,12 @@ export class ProcessReloadService implements ProcessReloadServiceAbstraction { await this.reloadCallback(); } return; + } else { + this.logService.info( + "[Process Reload Service] Desktop ipc fingerprint validated, preventing process reload", + ); } + if (this.reloadInterval == null) { this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000); } diff --git a/libs/common/src/models/data/event.data.ts b/libs/common/src/models/data/event.data.ts index e261e5fd3a1..0e5e6fea96c 100644 --- a/libs/common/src/models/data/event.data.ts +++ b/libs/common/src/models/data/event.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { EventType } from "../../enums"; diff --git a/libs/common/src/models/domain/domain-service.ts b/libs/common/src/models/domain/domain-service.ts index 9ff53cc8787..a6b5ecfdaac 100644 --- a/libs/common/src/models/domain/domain-service.ts +++ b/libs/common/src/models/domain/domain-service.ts @@ -21,5 +21,5 @@ export const UriMatchStrategy = { export type UriMatchStrategySetting = (typeof UriMatchStrategy)[keyof typeof UriMatchStrategy]; // using uniqueness properties of object shape over Set for ease of state storability -export type NeverDomains = { [id: string]: null }; +export type NeverDomains = { [id: string]: null | { bannerIsDismissed?: boolean } }; export type EquivalentDomains = string[][]; diff --git a/libs/common/src/models/export/card.export.ts b/libs/common/src/models/export/card.export.ts index 151b447e864..16dcf5e7ac0 100644 --- a/libs/common/src/models/export/card.export.ts +++ b/libs/common/src/models/export/card.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { Card as CardDomain } from "../../vault/models/domain/card"; import { CardView } from "../../vault/models/view/card.view"; diff --git a/libs/common/src/models/export/cipher-with-ids.export.ts b/libs/common/src/models/export/cipher-with-ids.export.ts index 4e6e9e9630d..5fd2c62b8e8 100644 --- a/libs/common/src/models/export/cipher-with-ids.export.ts +++ b/libs/common/src/models/export/cipher-with-ids.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Cipher as CipherDomain } from "../../vault/models/domain/cipher"; import { CipherView } from "../../vault/models/view/cipher.view"; diff --git a/libs/common/src/models/export/cipher.export.ts b/libs/common/src/models/export/cipher.export.ts index 432a2d4e250..e542d0dfc1f 100644 --- a/libs/common/src/models/export/cipher.export.ts +++ b/libs/common/src/models/export/cipher.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { CipherRepromptType } from "../../vault/enums/cipher-reprompt-type"; import { CipherType } from "../../vault/enums/cipher-type"; diff --git a/libs/common/src/models/export/collection-with-id.export.ts b/libs/common/src/models/export/collection-with-id.export.ts index 4be3fa0121e..ef850bc6039 100644 --- a/libs/common/src/models/export/collection-with-id.export.ts +++ b/libs/common/src/models/export/collection-with-id.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { CollectionExport } from "./collection.export"; diff --git a/libs/common/src/models/export/collection.export.ts b/libs/common/src/models/export/collection.export.ts index 8bc6d680065..89137c34875 100644 --- a/libs/common/src/models/export/collection.export.ts +++ b/libs/common/src/models/export/collection.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { EncString } from "../../platform/models/domain/enc-string"; diff --git a/libs/common/src/models/export/fido2-credential.export.ts b/libs/common/src/models/export/fido2-credential.export.ts index 4c60d148db0..d14dbedf18e 100644 --- a/libs/common/src/models/export/fido2-credential.export.ts +++ b/libs/common/src/models/export/fido2-credential.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { Fido2Credential } from "../../vault/models/domain/fido2-credential"; import { Fido2CredentialView } from "../../vault/models/view/fido2-credential.view"; diff --git a/libs/common/src/models/export/field.export.ts b/libs/common/src/models/export/field.export.ts index 5ba341af617..837bdc15bee 100644 --- a/libs/common/src/models/export/field.export.ts +++ b/libs/common/src/models/export/field.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { FieldType, LinkedIdType } from "../../vault/enums"; import { Field as FieldDomain } from "../../vault/models/domain/field"; diff --git a/libs/common/src/models/export/folder-with-id.export.ts b/libs/common/src/models/export/folder-with-id.export.ts index 1f1964bb4a1..31636cfcfe3 100644 --- a/libs/common/src/models/export/folder-with-id.export.ts +++ b/libs/common/src/models/export/folder-with-id.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Folder as FolderDomain } from "../../vault/models/domain/folder"; import { FolderView } from "../../vault/models/view/folder.view"; diff --git a/libs/common/src/models/export/folder.export.ts b/libs/common/src/models/export/folder.export.ts index 6a2a63a77d5..56a201be262 100644 --- a/libs/common/src/models/export/folder.export.ts +++ b/libs/common/src/models/export/folder.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { Folder as FolderDomain } from "../../vault/models/domain/folder"; import { FolderView } from "../../vault/models/view/folder.view"; diff --git a/libs/common/src/models/export/identity.export.ts b/libs/common/src/models/export/identity.export.ts index 6722333d79f..3977ddafa73 100644 --- a/libs/common/src/models/export/identity.export.ts +++ b/libs/common/src/models/export/identity.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { Identity as IdentityDomain } from "../../vault/models/domain/identity"; import { IdentityView } from "../../vault/models/view/identity.view"; diff --git a/libs/common/src/models/export/login-uri.export.ts b/libs/common/src/models/export/login-uri.export.ts index a0534460612..1197755f357 100644 --- a/libs/common/src/models/export/login-uri.export.ts +++ b/libs/common/src/models/export/login-uri.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { EncString } from "../../platform/models/domain/enc-string"; import { LoginUri as LoginUriDomain } from "../../vault/models/domain/login-uri"; diff --git a/libs/common/src/models/export/login.export.ts b/libs/common/src/models/export/login.export.ts index 6982d386c32..d24c084aa48 100644 --- a/libs/common/src/models/export/login.export.ts +++ b/libs/common/src/models/export/login.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { Login as LoginDomain } from "../../vault/models/domain/login"; import { LoginView } from "../../vault/models/view/login.view"; diff --git a/libs/common/src/models/export/password-history.export.ts b/libs/common/src/models/export/password-history.export.ts index fff22de8def..ece7e331009 100644 --- a/libs/common/src/models/export/password-history.export.ts +++ b/libs/common/src/models/export/password-history.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { Password } from "../../vault/models/domain/password"; import { PasswordHistoryView } from "../../vault/models/view/password-history.view"; diff --git a/libs/common/src/models/export/secure-note.export.ts b/libs/common/src/models/export/secure-note.export.ts index a9a0f78182a..358c5a19007 100644 --- a/libs/common/src/models/export/secure-note.export.ts +++ b/libs/common/src/models/export/secure-note.export.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecureNoteType } from "../../vault/enums"; import { SecureNote as SecureNoteDomain } from "../../vault/models/domain/secure-note"; import { SecureNoteView } from "../../vault/models/view/secure-note.view"; diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts index 86683e97e20..a99ebac34b3 100644 --- a/libs/common/src/models/export/ssh-key.export.ts +++ b/libs/common/src/models/export/ssh-key.export.ts @@ -1,7 +1,9 @@ -import { SshKeyView as SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../platform/models/domain/enc-string"; import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key"; +import { SshKeyView as SshKeyView } from "../../vault/models/view/ssh-key.view"; import { safeGetString } from "./utils"; diff --git a/libs/common/src/models/export/utils.ts b/libs/common/src/models/export/utils.ts index 630b4898503..b7e0b74b611 100644 --- a/libs/common/src/models/export/utils.ts +++ b/libs/common/src/models/export/utils.ts @@ -1,4 +1,4 @@ -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncString } from "../../platform/models/domain/enc-string"; export function safeGetString(value: string | EncString) { if (value == null) { diff --git a/libs/common/src/models/request/delete-recover.request.ts b/libs/common/src/models/request/delete-recover.request.ts index 02a019b8f6e..38670deb1d5 100644 --- a/libs/common/src/models/request/delete-recover.request.ts +++ b/libs/common/src/models/request/delete-recover.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class DeleteRecoverRequest { email: string; } diff --git a/libs/common/src/models/request/device-token.request.ts b/libs/common/src/models/request/device-token.request.ts index 99ca69a2f5d..47308261a38 100644 --- a/libs/common/src/models/request/device-token.request.ts +++ b/libs/common/src/models/request/device-token.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class DeviceTokenRequest { pushToken: string; diff --git a/libs/common/src/models/request/event.request.ts b/libs/common/src/models/request/event.request.ts index 62d6a0b43d5..ec875624094 100644 --- a/libs/common/src/models/request/event.request.ts +++ b/libs/common/src/models/request/event.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EventType } from "../../enums"; export class EventRequest { diff --git a/libs/common/src/models/request/import-directory-request-group.ts b/libs/common/src/models/request/import-directory-request-group.ts index 4b7b3567c7f..d862c8ecd2f 100644 --- a/libs/common/src/models/request/import-directory-request-group.ts +++ b/libs/common/src/models/request/import-directory-request-group.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ImportDirectoryRequestGroup { name: string; externalId: string; diff --git a/libs/common/src/models/request/import-directory-request-user.ts b/libs/common/src/models/request/import-directory-request-user.ts index 9dbf6a3433b..56d4b8f7f14 100644 --- a/libs/common/src/models/request/import-directory-request-user.ts +++ b/libs/common/src/models/request/import-directory-request-user.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class ImportDirectoryRequestUser { externalId: string; email: string; diff --git a/libs/common/src/models/request/kdf.request.ts b/libs/common/src/models/request/kdf.request.ts index 8f27b0ec10f..f0bd376317f 100644 --- a/libs/common/src/models/request/kdf.request.ts +++ b/libs/common/src/models/request/kdf.request.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { KdfType } from "@bitwarden/key-management"; + import { PasswordRequest } from "../../auth/models/request/password.request"; -import { KdfType } from "../../platform/enums"; export class KdfRequest extends PasswordRequest { kdf: KdfType; diff --git a/libs/common/src/models/request/reference-event.request.ts b/libs/common/src/models/request/reference-event.request.ts index 7b8f33a2371..70ea24df8ac 100644 --- a/libs/common/src/models/request/reference-event.request.ts +++ b/libs/common/src/models/request/reference-event.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export type InitiationPath = | "Registration form" | "Password Manager trial from marketing website" diff --git a/libs/common/src/models/request/register.request.ts b/libs/common/src/models/request/register.request.ts index f01d89f4b22..4f11aadd49c 100644 --- a/libs/common/src/models/request/register.request.ts +++ b/libs/common/src/models/request/register.request.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { KdfType } from "@bitwarden/key-management"; + import { CaptchaProtectedRequest } from "../../auth/models/request/captcha-protected.request"; -import { KdfType } from "../../platform/enums"; import { KeysRequest } from "./keys.request"; import { ReferenceEventRequest } from "./reference-event.request"; diff --git a/libs/common/src/models/request/seat.request.ts b/libs/common/src/models/request/seat.request.ts index d60e41fade4..766eacac37a 100644 --- a/libs/common/src/models/request/seat.request.ts +++ b/libs/common/src/models/request/seat.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SeatRequest { seatAdjustment: number; } diff --git a/libs/common/src/models/request/storage.request.ts b/libs/common/src/models/request/storage.request.ts index 4b3b614d09c..a208712dc10 100644 --- a/libs/common/src/models/request/storage.request.ts +++ b/libs/common/src/models/request/storage.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class StorageRequest { storageGbAdjustment: number; } diff --git a/libs/common/src/models/request/update-domains.request.ts b/libs/common/src/models/request/update-domains.request.ts index 528d36829e3..c9680735335 100644 --- a/libs/common/src/models/request/update-domains.request.ts +++ b/libs/common/src/models/request/update-domains.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class UpdateDomainsRequest { equivalentDomains: string[][]; excludedGlobalEquivalentDomains: number[]; diff --git a/libs/common/src/models/request/verify-bank.request.ts b/libs/common/src/models/request/verify-bank.request.ts index 823eaf46165..b827b875949 100644 --- a/libs/common/src/models/request/verify-bank.request.ts +++ b/libs/common/src/models/request/verify-bank.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class VerifyBankRequest { amount1: number; amount2: number; diff --git a/libs/common/src/models/response/base.response.ts b/libs/common/src/models/response/base.response.ts index 95a6dab8f39..a8a185bf710 100644 --- a/libs/common/src/models/response/base.response.ts +++ b/libs/common/src/models/response/base.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class BaseResponse { private response: any; diff --git a/libs/common/src/models/response/error.response.ts b/libs/common/src/models/response/error.response.ts index 43e30df0eaa..de88db04922 100644 --- a/libs/common/src/models/response/error.response.ts +++ b/libs/common/src/models/response/error.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Utils } from "../../platform/misc/utils"; import { BaseResponse } from "./base.response"; diff --git a/libs/common/src/models/response/notification.response.ts b/libs/common/src/models/response/notification.response.ts index af79b883f08..894a00ee885 100644 --- a/libs/common/src/models/response/notification.response.ts +++ b/libs/common/src/models/response/notification.response.ts @@ -42,6 +42,12 @@ export class NotificationResponse extends BaseResponse { case NotificationType.AuthRequestResponse: this.payload = new AuthRequestPushNotification(payload); break; + case NotificationType.SyncOrganizationStatusChanged: + this.payload = new OrganizationStatusPushNotification(payload); + break; + case NotificationType.SyncOrganizationCollectionSettingChanged: + this.payload = new OrganizationCollectionSettingChangedPushNotification(payload); + break; default: break; } @@ -112,3 +118,28 @@ export class AuthRequestPushNotification extends BaseResponse { this.userId = this.getResponseProperty("UserId"); } } + +export class OrganizationStatusPushNotification extends BaseResponse { + organizationId: string; + enabled: boolean; + + constructor(response: any) { + super(response); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.enabled = this.getResponseProperty("Enabled"); + } +} + +export class OrganizationCollectionSettingChangedPushNotification extends BaseResponse { + organizationId: string; + limitCollectionCreation: boolean; + limitCollectionDeletion: boolean; + + constructor(response: any) { + super(response); + + this.organizationId = this.getResponseProperty("OrganizationId"); + this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation"); + this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion"); + } +} diff --git a/libs/common/src/platform/abstractions/config/config.service.ts b/libs/common/src/platform/abstractions/config/config.service.ts index 05a3dcd148c..04f150838e4 100644 --- a/libs/common/src/platform/abstractions/config/config.service.ts +++ b/libs/common/src/platform/abstractions/config/config.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { SemVer } from "semver"; diff --git a/libs/common/src/platform/abstractions/config/server-config.ts b/libs/common/src/platform/abstractions/config/server-config.ts index b51628cbf5b..f77239b3016 100644 --- a/libs/common/src/platform/abstractions/config/server-config.ts +++ b/libs/common/src/platform/abstractions/config/server-config.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum"; diff --git a/libs/common/src/platform/abstractions/crypto-function.service.ts b/libs/common/src/platform/abstractions/crypto-function.service.ts index 18c14677dd0..56b0ee55afe 100644 --- a/libs/common/src/platform/abstractions/crypto-function.service.ts +++ b/libs/common/src/platform/abstractions/crypto-function.service.ts @@ -1,5 +1,5 @@ import { CsprngArray } from "../../types/csprng"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export abstract class CryptoFunctionService { @@ -51,11 +51,13 @@ export abstract class CryptoFunctionService { iv: string, mac: string, key: SymmetricCryptoKey, - ): DecryptParameters; - abstract aesDecryptFast( - parameters: DecryptParameters, - mode: "cbc" | "ecb", - ): Promise; + ): CbcDecryptParameters; + abstract aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise; abstract aesDecrypt( data: Uint8Array, iv: Uint8Array, diff --git a/libs/common/src/platform/abstractions/encrypt.service.ts b/libs/common/src/platform/abstractions/encrypt.service.ts index 5b28b98803b..a660524699d 100644 --- a/libs/common/src/platform/abstractions/encrypt.service.ts +++ b/libs/common/src/platform/abstractions/encrypt.service.ts @@ -8,12 +8,32 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export abstract class EncryptService { abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise; abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an EncString to a string + * @param encString - The EncString to decrypt + * @param key - The key to decrypt the EncString with + * @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include + * sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt + * @returns The decrypted string + */ abstract decryptToUtf8( encString: EncString, key: SymmetricCryptoKey, - decryptContext?: string, + decryptTrace?: string, ): Promise; - abstract decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an Encrypted object to a Uint8Array + * @param encThing - The Encrypted object to decrypt + * @param key - The key to decrypt the Encrypted object with + * @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include + * sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt + * @returns The decrypted Uint8Array + */ + abstract decryptToBytes( + encThing: Encrypted, + key: SymmetricCryptoKey, + decryptTrace?: string, + ): Promise; abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; abstract resolveLegacyKey(key: SymmetricCryptoKey, encThing: Encrypted): SymmetricCryptoKey; diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index 0293d68903c..8d32fc4231d 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -128,5 +128,10 @@ export abstract class EnvironmentService { /** * Get the environment from state. Useful if you need to get the environment for another user. */ + abstract getEnvironment$(userId?: string): Observable; + + /** + * @deprecated Use {@link getEnvironment$} instead. + */ abstract getEnvironment(userId?: string): Promise; } diff --git a/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts index 797133edd22..ffb78d51bd3 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, Subject } from "rxjs"; import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; diff --git a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts index 535248e7ecd..e9e68ca92c3 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; /** @@ -6,7 +8,7 @@ import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential * * The authenticator provides key management and cryptographic signatures. */ -export abstract class Fido2AuthenticatorService { +export abstract class Fido2AuthenticatorService { /** * Create and save a new credential as described in: * https://www.w3.org/TR/webauthn-3/#sctn-op-make-cred @@ -17,7 +19,7 @@ export abstract class Fido2AuthenticatorService { **/ makeCredential: ( params: Fido2AuthenticatorMakeCredentialsParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ) => Promise; @@ -31,7 +33,7 @@ export abstract class Fido2AuthenticatorService { */ getAssertion: ( params: Fido2AuthenticatorGetAssertionParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ) => Promise; @@ -64,7 +66,7 @@ export class Fido2AuthenticatorError extends Error { } export interface PublicKeyCredentialDescriptor { - id: BufferSource; + id: Uint8Array; transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; type: "public-key"; } diff --git a/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts index 6467af5fc1c..55d9cce8049 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export const UserRequestedFallbackAbortReason = "UserRequestedFallback"; export type UserVerification = "discouraged" | "preferred" | "required"; @@ -13,7 +15,7 @@ export type UserVerification = "discouraged" | "preferred" | "required"; * It is responsible for both marshalling the inputs for the underlying authenticator operations, * and for returning the results of the latter operations to the Web Authentication API's callers. */ -export abstract class Fido2ClientService { +export abstract class Fido2ClientService { isFido2FeatureEnabled: (hostname: string, origin: string) => Promise; /** @@ -26,7 +28,7 @@ export abstract class Fido2ClientService { */ createCredential: ( params: CreateCredentialParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ) => Promise; @@ -41,7 +43,7 @@ export abstract class Fido2ClientService { */ assertCredential: ( params: AssertCredentialParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ) => Promise; } diff --git a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts index 70fe0b092be..7beefc3b4cc 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /** * Parameters used to ask the user to confirm the creation of a new credential. */ @@ -59,7 +61,7 @@ export interface PickCredentialParams { * The service is session based and is intended to be used by the FIDO2 authenticator to open a window, * and then use this window to ask the user for input and/or display messages to the user. */ -export abstract class Fido2UserInterfaceService { +export abstract class Fido2UserInterfaceService { /** * Creates a new session. * Note: This will not necessarily open a window until it is needed to request something from the user. @@ -69,7 +71,7 @@ export abstract class Fido2UserInterfaceService { */ newSession: ( fallbackSupported: boolean, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ) => Promise; } diff --git a/libs/common/src/platform/abstractions/file-download/file-download.builder.ts b/libs/common/src/platform/abstractions/file-download/file-download.builder.ts index 8db379fec73..e0dc673d998 100644 --- a/libs/common/src/platform/abstractions/file-download/file-download.builder.ts +++ b/libs/common/src/platform/abstractions/file-download/file-download.builder.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FileDownloadRequest } from "./file-download.request"; export class FileDownloadBuilder { diff --git a/libs/common/src/platform/abstractions/key-generation.service.ts b/libs/common/src/platform/abstractions/key-generation.service.ts index 5c6119919a3..8314efe3469 100644 --- a/libs/common/src/platform/abstractions/key-generation.service.ts +++ b/libs/common/src/platform/abstractions/key-generation.service.ts @@ -1,4 +1,5 @@ -import { KdfConfig } from "../../auth/models/domain/kdf-config"; +import { KdfConfig } from "@bitwarden/key-management"; + import { CsprngArray } from "../../types/csprng"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 00c14bdfaca..d44d38c36ab 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { BitwardenClient } from "@bitwarden/sdk-internal"; @@ -6,9 +8,9 @@ import { UserId } from "../../../types/guid"; export abstract class SdkService { /** - * Check if the SDK is supported in the current environment. + * Retrieve the version of the SDK. */ - supported$: Observable; + version$: Observable; /** * Retrieve a client initialized without a user. @@ -28,6 +30,4 @@ export abstract class SdkService { * @param userId */ abstract userClient$(userId: UserId): Observable; - - abstract failedToInitialize(category: string, error?: Error): Promise; } diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 619bab8f034..123c70c298a 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BiometricKey } from "../../auth/types/biometric-key"; import { Account } from "../models/domain/account"; import { StorageOptions } from "../models/domain/storage-options"; diff --git a/libs/common/src/platform/enums/index.ts b/libs/common/src/platform/enums/index.ts index bac3f4ec41a..389f867e645 100644 --- a/libs/common/src/platform/enums/index.ts +++ b/libs/common/src/platform/enums/index.ts @@ -2,7 +2,6 @@ export * from "./encryption-type.enum"; export * from "./file-upload-type.enum"; export * from "./hash-purpose.enum"; export * from "./html-storage-location.enum"; -export * from "./kdf-type.enum"; export * from "./key-suffix-options.enum"; export * from "./log-level-type.enum"; export * from "./storage-location.enum"; diff --git a/libs/common/src/platform/enums/key-suffix-options.enum.ts b/libs/common/src/platform/enums/key-suffix-options.enum.ts index b268c4b777f..98fa215be6a 100644 --- a/libs/common/src/platform/enums/key-suffix-options.enum.ts +++ b/libs/common/src/platform/enums/key-suffix-options.enum.ts @@ -1,5 +1,4 @@ export enum KeySuffixOptions { Auto = "auto", - Biometric = "biometric", Pin = "pin", } diff --git a/libs/common/src/platform/factories/global-state-factory.ts b/libs/common/src/platform/factories/global-state-factory.ts index 3ce8e6469a2..b52b022fd18 100644 --- a/libs/common/src/platform/factories/global-state-factory.ts +++ b/libs/common/src/platform/factories/global-state-factory.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { GlobalState } from "../models/domain/global-state"; export class GlobalStateFactory { diff --git a/libs/common/src/platform/messaging/types.ts b/libs/common/src/platform/messaging/types.ts index 0461132a0a8..7d3c9a74d78 100644 --- a/libs/common/src/platform/messaging/types.ts +++ b/libs/common/src/platform/messaging/types.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore declare const tag: unique symbol; /** diff --git a/libs/common/src/platform/misc/convert-values.ts b/libs/common/src/platform/misc/convert-values.ts index 7a1087ec360..dd097c6096d 100644 --- a/libs/common/src/platform/misc/convert-values.ts +++ b/libs/common/src/platform/misc/convert-values.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ObservableInput, OperatorFunction, map } from "rxjs"; /** diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index 297e616e66b..30531b6799e 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -1,17 +1,17 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { - showPasswordless?: boolean; sdk?: boolean; prereleaseBuild?: boolean; }; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedDevFlags = { noopNotifications: boolean; skipWelcomeOnInstall: boolean; configRetrievalIntervalMs: number; + showRiskInsightsDebug: boolean; }; function getFlags(envFlags: string | T): T { diff --git a/libs/common/src/platform/misc/sequentialize.ts b/libs/common/src/platform/misc/sequentialize.ts index 852c32db56a..0400c8b1d0b 100644 --- a/libs/common/src/platform/misc/sequentialize.ts +++ b/libs/common/src/platform/misc/sequentialize.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore const caches = new Map>>(); const getCache = (obj: any) => { diff --git a/libs/common/src/platform/misc/throttle.ts b/libs/common/src/platform/misc/throttle.ts index dbbbd5761d3..643cce8f6ba 100644 --- a/libs/common/src/platform/misc/throttle.ts +++ b/libs/common/src/platform/misc/throttle.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /** * Use as a Decorator on async functions, it will limit how many times the function can be * in-flight at a time. diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index a7cc05bbf64..f654897e9e2 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /* eslint-disable no-useless-escape */ import * as path from "path"; @@ -6,10 +8,14 @@ import { Observable, of, switchMap } from "rxjs"; import { getHostname, parse } from "tldts"; import { Merge } from "type-fest"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports const nodeURL = typeof self === "undefined" ? require("url") : null; declare global { @@ -606,6 +612,8 @@ export class Utils { } return new URL(uriString); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error } diff --git a/libs/common/src/platform/models/data/server-config.data.ts b/libs/common/src/platform/models/data/server-config.data.ts index d5f17fd0ace..6ed51d2f5ce 100644 --- a/libs/common/src/platform/models/data/server-config.data.ts +++ b/libs/common/src/platform/models/data/server-config.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum"; diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 199b99a8e7e..9873e5c8574 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { DeepJsonify } from "../../../types/deep-jsonify"; diff --git a/libs/common/src/platform/models/domain/decrypt-parameters.ts b/libs/common/src/platform/models/domain/decrypt-parameters.ts index 09a4c1d8f40..d3b4bf60d42 100644 --- a/libs/common/src/platform/models/domain/decrypt-parameters.ts +++ b/libs/common/src/platform/models/domain/decrypt-parameters.ts @@ -1,8 +1,13 @@ -export class DecryptParameters { +export type CbcDecryptParameters = { encKey: T; data: T; iv: T; - macKey: T; - mac: T; + macKey?: T; + mac?: T; macData: T; -} +}; + +export type EcbDecryptParameters = { + encKey: T; + data: T; +}; diff --git a/libs/common/src/platform/models/domain/domain-base.spec.ts b/libs/common/src/platform/models/domain/domain-base.spec.ts index 0bdee21e3c1..80a4e5e8606 100644 --- a/libs/common/src/platform/models/domain/domain-base.spec.ts +++ b/libs/common/src/platform/models/domain/domain-base.spec.ts @@ -67,9 +67,13 @@ describe("DomainBase", () => { ); // @ts-expect-error -- encString2 was not decrypted + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: string; plainText: string }; // encString2 was not decrypted, so it's still an EncString + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: EncString; plainText: string }; }); diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index 1cfcfac02ff..110a1dc7208 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ConditionalExcept, ConditionalKeys, Constructor } from "type-fest"; import { View } from "../../../models/view/view"; @@ -6,7 +8,7 @@ import { EncryptService } from "../../abstractions/encrypt.service"; import { EncString } from "./enc-string"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type EncStringKeys = ConditionalKeys, EncString>; export type DecryptedObject< TEncryptedObject, @@ -61,6 +63,7 @@ export default class Domain { map: any, orgId: string, key: SymmetricCryptoKey = null, + objectContext: string = "No Domain Context", ): Promise { const promises = []; const self: any = this; @@ -76,7 +79,11 @@ export default class Domain { .then(() => { const mapProp = map[theProp] || theProp; if (self[mapProp]) { - return self[mapProp].decrypt(orgId, key); + return self[mapProp].decrypt( + orgId, + key, + `Property: ${prop}; ObjectContext: ${objectContext}`, + ); } return null; }) @@ -112,12 +119,21 @@ export default class Domain { key: SymmetricCryptoKey, encryptService: EncryptService, _: Constructor = this.constructor as Constructor, + objectContext: string = "No Domain Context", ): Promise> { const promises = []; for (const prop of encryptedProperties) { const value = (this as any)[prop] as EncString; - promises.push(this.decryptProperty(prop, value, key, encryptService)); + promises.push( + this.decryptProperty( + prop, + value, + key, + encryptService, + `Property: ${prop.toString()}; ObjectContext: ${objectContext}`, + ), + ); } const decryptedObjects = await Promise.all(promises); @@ -135,10 +151,11 @@ export default class Domain { value: EncString, key: SymmetricCryptoKey, encryptService: EncryptService, + decryptTrace: string, ) { let decrypted: string = null; if (value) { - decrypted = await value.decryptWithKey(key, encryptService); + decrypted = await value.decryptWithKey(key, encryptService, decryptTrace); } else { decrypted = null; } diff --git a/libs/common/src/platform/models/domain/enc-array-buffer.ts b/libs/common/src/platform/models/domain/enc-array-buffer.ts index 118bfb6cc3b..305504f57b7 100644 --- a/libs/common/src/platform/models/domain/enc-array-buffer.ts +++ b/libs/common/src/platform/models/domain/enc-array-buffer.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Utils } from "../../../platform/misc/utils"; import { EncryptionType } from "../../enums"; import { Encrypted } from "../../interfaces/encrypted"; diff --git a/libs/common/src/platform/models/domain/enc-string.spec.ts b/libs/common/src/platform/models/domain/enc-string.spec.ts index 85108a9609b..b4916b9f70a 100644 --- a/libs/common/src/platform/models/domain/enc-string.spec.ts +++ b/libs/common/src/platform/models/domain/enc-string.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeEncString, makeStaticByteArray } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index 6f01f46439c..f148664a4f9 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify, Opaque } from "type-fest"; import { EncryptService } from "../../abstractions/encrypt.service"; @@ -123,6 +125,8 @@ export class EncString implements Encrypted { try { encType = parseInt(headerPieces[0], null); encPieces = headerPieces[1].split("|"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return { encType: NaN, encPieces: [] }; } @@ -154,21 +158,21 @@ export class EncString implements Encrypted { return EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE[encType] === encPieces.length; } - async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { + async decrypt(orgId: string, key: SymmetricCryptoKey = null, context?: string): Promise { if (this.decryptedValue != null) { return this.decryptedValue; } - let keyContext = "provided-key"; + let decryptTrace = "provided-key"; try { if (key == null) { key = await this.getKeyForDecryption(orgId); - keyContext = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey"; + decryptTrace = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey"; if (orgId != null) { - keyContext = `domain-orgkey-${orgId}`; + decryptTrace = `domain-orgkey-${orgId}`; } else { const cryptoService = Utils.getContainerService().getKeyService(); - keyContext = + decryptTrace = (await cryptoService.getUserKey()) == null ? "domain-withlegacysupport-masterkey" : "domain-withlegacysupport-userkey"; @@ -179,20 +183,32 @@ export class EncString implements Encrypted { } const encryptService = Utils.getContainerService().getEncryptService(); - this.decryptedValue = await encryptService.decryptToUtf8(this, key, keyContext); + this.decryptedValue = await encryptService.decryptToUtf8( + this, + key, + decryptTrace == null ? context : `${decryptTrace}${context || ""}`, + ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } return this.decryptedValue; } - async decryptWithKey(key: SymmetricCryptoKey, encryptService: EncryptService) { + async decryptWithKey( + key: SymmetricCryptoKey, + encryptService: EncryptService, + decryptTrace: string = "domain-withkey", + ): Promise { try { if (key == null) { throw new Error("No key to decrypt EncString"); } - this.decryptedValue = await encryptService.decryptToUtf8(this, key, "domain-withkey"); + this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } diff --git a/libs/common/src/platform/models/domain/encrypted-object.ts b/libs/common/src/platform/models/domain/encrypted-object.ts index 22d5a388114..92153b27636 100644 --- a/libs/common/src/platform/models/domain/encrypted-object.ts +++ b/libs/common/src/platform/models/domain/encrypted-object.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; export class EncryptedObject { diff --git a/libs/common/src/platform/models/domain/state.ts b/libs/common/src/platform/models/domain/state.ts index 5dde49f99db..d9f5849a3ca 100644 --- a/libs/common/src/platform/models/domain/state.ts +++ b/libs/common/src/platform/models/domain/state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Account } from "./account"; diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index 37d03bc0646..eab4c7b2114 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Utils } from "../../../platform/misc/utils"; @@ -5,7 +7,7 @@ import { EncryptionType } from "../../enums"; export class SymmetricCryptoKey { key: Uint8Array; - encKey?: Uint8Array; + encKey: Uint8Array; macKey?: Uint8Array; encType: EncryptionType; @@ -46,12 +48,8 @@ export class SymmetricCryptoKey { throw new Error("Unsupported encType/key length."); } - if (this.key != null) { - this.keyB64 = Utils.fromBufferToB64(this.key); - } - if (this.encKey != null) { - this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - } + this.keyB64 = Utils.fromBufferToB64(this.key); + this.encKeyB64 = Utils.fromBufferToB64(this.encKey); if (this.macKey != null) { this.macKeyB64 = Utils.fromBufferToB64(this.macKey); } diff --git a/libs/common/src/platform/models/response/server-config.response.ts b/libs/common/src/platform/models/response/server-config.response.ts index d295634830a..cae0603ea1e 100644 --- a/libs/common/src/platform/models/response/server-config.response.ts +++ b/libs/common/src/platform/models/response/server-config.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum"; import { BaseResponse } from "../../../models/response/base.response"; import { Region } from "../../abstractions/environment.service"; diff --git a/libs/common/src/platform/scheduling/task-scheduler.service.ts b/libs/common/src/platform/scheduling/task-scheduler.service.ts index 57e5291f7c6..a38a03e0076 100644 --- a/libs/common/src/platform/scheduling/task-scheduler.service.ts +++ b/libs/common/src/platform/scheduling/task-scheduler.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Subscription } from "rxjs"; import { ScheduledTaskName } from "./scheduled-task-name.enum"; diff --git a/libs/common/src/platform/services/config/default-config.service.ts b/libs/common/src/platform/services/config/default-config.service.ts index fce1c12106f..cc52a5b8dad 100644 --- a/libs/common/src/platform/services/config/default-config.service.ts +++ b/libs/common/src/platform/services/config/default-config.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatest, firstValueFrom, diff --git a/libs/common/src/platform/services/console-log.service.ts b/libs/common/src/platform/services/console-log.service.ts index a1480a0c267..cb6554e2aa2 100644 --- a/libs/common/src/platform/services/console-log.service.ts +++ b/libs/common/src/platform/services/console-log.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService as LogServiceAbstraction } from "../abstractions/log.service"; import { LogLevelType } from "../enums/log-level-type.enum"; diff --git a/libs/common/src/platform/services/container.service.ts b/libs/common/src/platform/services/container.service.ts index 6022e097ab0..c3e727a2e1e 100644 --- a/libs/common/src/platform/services/container.service.ts +++ b/libs/common/src/platform/services/container.service.ts @@ -1,3 +1,5 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { EncryptService } from "../abstractions/encrypt.service"; diff --git a/libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts b/libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts index d3bbc82905a..1320fbae0e0 100644 --- a/libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts +++ b/libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, fromEvent, filter, map, takeUntil, defaultIfEmpty, Subject } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts index 137d67ca0f0..68263cadf27 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts +++ b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Utils } from "../../../platform/misc/utils"; import { CryptoFunctionService } from "../../abstractions/crypto-function.service"; import { EncryptService } from "../../abstractions/encrypt.service"; @@ -112,7 +114,7 @@ export class EncryptServiceImplementation implements EncryptService { const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); if (!macsEqual) { this.logMacFailed( - "[Encrypt service] MAC comparison failed. Key or payload has changed. Key type " + + "[Encrypt service] decryptToUtf8 MAC comparison failed. Key or payload has changed. Key type " + encryptionTypeName(key.encType) + "Payload type " + encryptionTypeName(encString.encryptionType) + @@ -123,10 +125,14 @@ export class EncryptServiceImplementation implements EncryptService { } } - return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc"); + return await this.cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters: fastParams }); } - async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise { + async decryptToBytes( + encThing: Encrypted, + key: SymmetricCryptoKey, + decryptContext: string = "no context", + ): Promise { if (key == null) { throw new Error("No encryption key provided."); } @@ -143,7 +149,9 @@ export class EncryptServiceImplementation implements EncryptService { "[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -153,7 +161,9 @@ export class EncryptServiceImplementation implements EncryptService { "[Encrypt service] Key encryption type does not match payload encryption type. Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -165,11 +175,13 @@ export class EncryptServiceImplementation implements EncryptService { const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256"); if (computedMac === null) { this.logMacFailed( - "[Encrypt service] Failed to compute MAC." + + "[Encrypt service#decryptToBytes] Failed to compute MAC." + " Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -177,11 +189,13 @@ export class EncryptServiceImplementation implements EncryptService { const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); if (!macsMatch) { this.logMacFailed( - "[Encrypt service] MAC comparison failed. Key or payload has changed." + + "[Encrypt service#decryptToBytes]: MAC comparison failed. Key or payload has changed." + " Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } diff --git a/libs/common/src/platform/services/cryptography/encrypt.worker.ts b/libs/common/src/platform/services/cryptography/encrypt.worker.ts index 047b7a9556a..a293e1c6bb0 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.worker.ts +++ b/libs/common/src/platform/services/cryptography/encrypt.worker.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Decryptable } from "../../interfaces/decryptable.interface"; diff --git a/libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts b/libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts index 44dc5a8bf76..7a4fd8f3c1d 100644 --- a/libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts +++ b/libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BulkEncryptService } from "../../abstractions/bulk-encrypt.service"; import { EncryptService } from "../../abstractions/encrypt.service"; import { Decryptable } from "../../interfaces/decryptable.interface"; diff --git a/libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts b/libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts index 227db77526a..100dcf152e6 100644 --- a/libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts +++ b/libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { defaultIfEmpty, filter, firstValueFrom, fromEvent, map, Subject, takeUntil } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/common/src/platform/services/default-environment.service.spec.ts b/libs/common/src/platform/services/default-environment.service.spec.ts index 7d266e93fc3..870f887c160 100644 --- a/libs/common/src/platform/services/default-environment.service.spec.ts +++ b/libs/common/src/platform/services/default-environment.service.spec.ts @@ -314,7 +314,7 @@ describe("EnvironmentService", () => { await switchUser(testUser); - const env = await sut.getEnvironment(); + const env = await firstValueFrom(sut.getEnvironment$()); expect(env.getHostname()).toBe(expectedHost); }); @@ -325,7 +325,7 @@ describe("EnvironmentService", () => { setGlobalData(region, new EnvironmentUrls()); setUserData(Region.US, new EnvironmentUrls()); - const env = await sut.getEnvironment(); + const env = await firstValueFrom(sut.getEnvironment$()); expect(env.getHostname()).toBe(expectedHost); }); @@ -338,7 +338,7 @@ describe("EnvironmentService", () => { setGlobalData(region, new EnvironmentUrls()); setUserData(Region.US, new EnvironmentUrls()); - const env = await sut.getEnvironment(testUser); + const env = await firstValueFrom(sut.getEnvironment$(testUser)); expect(env.getHostname()).toBe(expectedHost); }, ); @@ -355,7 +355,7 @@ describe("EnvironmentService", () => { await switchUser(testUser); - const env = await sut.getEnvironment(alternateTestUser); + const env = await firstValueFrom(sut.getEnvironment$(alternateTestUser)); expect(env.getHostname()).toBe(expectedHost); }, ); @@ -366,7 +366,7 @@ describe("EnvironmentService", () => { setGlobalData(Region.SelfHosted, globalSelfHostUrls); setUserData(Region.EU, new EnvironmentUrls()); - const env = await sut.getEnvironment(); + const env = await firstValueFrom(sut.getEnvironment$()); expect(env.getHostname()).toBe("base.example.com"); }); @@ -377,7 +377,7 @@ describe("EnvironmentService", () => { setGlobalData(Region.SelfHosted, globalSelfHostUrls); setUserData(Region.EU, new EnvironmentUrls()); - const env = await sut.getEnvironment(); + const env = await firstValueFrom(sut.getEnvironment$()); expect(env.getHostname()).toBe("vault.example.com"); }); @@ -391,7 +391,7 @@ describe("EnvironmentService", () => { await switchUser(testUser); - const env = await sut.getEnvironment(alternateTestUser); + const env = await firstValueFrom(sut.getEnvironment$(alternateTestUser)); expect(env.getHostname()).toBe("base.example.com"); }); }); diff --git a/libs/common/src/platform/services/default-environment.service.ts b/libs/common/src/platform/services/default-environment.service.ts index 8ed673d066e..ac3e39b2bb3 100644 --- a/libs/common/src/platform/services/default-environment.service.ts +++ b/libs/common/src/platform/services/default-environment.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { distinctUntilChanged, firstValueFrom, map, Observable, switchMap } from "rxjs"; import { Jsonify } from "type-fest"; @@ -269,23 +271,30 @@ export class DefaultEnvironmentService implements EnvironmentService { } } - async getEnvironment(userId?: UserId): Promise { + getEnvironment$(userId?: UserId): Observable { if (userId == null) { - return await firstValueFrom(this.environment$); + return this.environment$; } - const state = await this.getEnvironmentState(userId); - return this.buildEnvironment(state.region, state.urls); + return this.activeAccountId$.pipe( + switchMap((activeUserId) => { + // Previous rules dictated that we only get from user scoped state if there is an active user. + if (activeUserId == null) { + return this.globalState.state$; + } + return this.stateProvider.getUser(userId ?? activeUserId, USER_ENVIRONMENT_KEY).state$; + }), + map((state) => { + return this.buildEnvironment(state?.region, state?.urls); + }), + ); } - private async getEnvironmentState(userId: UserId | null) { - // Previous rules dictated that we only get from user scoped state if there is an active user. - const activeUserId = await firstValueFrom(this.activeAccountId$); - return activeUserId == null - ? await firstValueFrom(this.globalState.state$) - : await firstValueFrom( - this.stateProvider.getUser(userId ?? activeUserId, USER_ENVIRONMENT_KEY).state$, - ); + /** + * @deprecated Use getEnvironment$ instead. + */ + async getEnvironment(userId?: UserId): Promise { + return firstValueFrom(this.getEnvironment$(userId)); } async seedUserEnvironment(userId: UserId) { diff --git a/libs/common/src/platform/services/default-server-settings.service.ts b/libs/common/src/platform/services/default-server-settings.service.ts index 9d0dd4bfd94..2568877efd4 100644 --- a/libs/common/src/platform/services/default-server-settings.service.ts +++ b/libs/common/src/platform/services/default-server-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { map } from "rxjs/operators"; diff --git a/libs/common/src/platform/services/fido2/credential-id-utils.spec.ts b/libs/common/src/platform/services/fido2/credential-id-utils.spec.ts new file mode 100644 index 00000000000..76e068ac01c --- /dev/null +++ b/libs/common/src/platform/services/fido2/credential-id-utils.spec.ts @@ -0,0 +1,68 @@ +import { compareCredentialIds, parseCredentialId } from "./credential-id-utils"; + +describe("credential-id-utils", () => { + describe("parseCredentialId", () => { + it("returns credentialId in binary format when given a valid UUID string", () => { + const result = parseCredentialId("08d70b74-e9f5-4522-a425-e5dcd40107e7"); + + expect(result).toEqual( + new Uint8Array([ + 0x08, 0xd7, 0x0b, 0x74, 0xe9, 0xf5, 0x45, 0x22, 0xa4, 0x25, 0xe5, 0xdc, 0xd4, 0x01, 0x07, + 0xe7, + ]), + ); + }); + + it("returns credentialId in binary format when given a valid Base64Url string", () => { + const result = parseCredentialId("b64.CNcLdOn1RSKkJeXc1AEH5w"); + + expect(result).toEqual( + new Uint8Array([ + 0x08, 0xd7, 0x0b, 0x74, 0xe9, 0xf5, 0x45, 0x22, 0xa4, 0x25, 0xe5, 0xdc, 0xd4, 0x01, 0x07, + 0xe7, + ]), + ); + }); + + it("returns undefined when given an invalid Base64 string", () => { + const result = parseCredentialId("b64.#$%&"); + + expect(result).toBeUndefined(); + }); + + it("returns undefined when given an invalid UUID string", () => { + const result = parseCredentialId("invalid"); + + expect(result).toBeUndefined(); + }); + }); + + describe("compareCredentialIds", () => { + it("returns true when the two credential IDs are equal", () => { + const a = new Uint8Array([0x01, 0x02, 0x03]); + const b = new Uint8Array([0x01, 0x02, 0x03]); + + const result = compareCredentialIds(a, b); + + expect(result).toBe(true); + }); + + it("returns false when the two credential IDs are not equal", () => { + const a = new Uint8Array([0x01, 0x02, 0x03]); + const b = new Uint8Array([0x01, 0x02, 0x04]); + + const result = compareCredentialIds(a, b); + + expect(result).toBe(false); + }); + + it("returns false when the two credential IDs have different lengths", () => { + const a = new Uint8Array([0x01, 0x02, 0x03]); + const b = new Uint8Array([0x01, 0x02, 0x03, 0x04]); + + const result = compareCredentialIds(a, b); + + expect(result).toBe(false); + }); + }); +}); diff --git a/libs/common/src/platform/services/fido2/credential-id-utils.ts b/libs/common/src/platform/services/fido2/credential-id-utils.ts new file mode 100644 index 00000000000..685669f0da3 --- /dev/null +++ b/libs/common/src/platform/services/fido2/credential-id-utils.ts @@ -0,0 +1,33 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Fido2Utils } from "./fido2-utils"; +import { guidToRawFormat } from "./guid-utils"; + +export function parseCredentialId(encodedCredentialId: string): Uint8Array { + try { + if (encodedCredentialId.startsWith("b64.")) { + return Fido2Utils.stringToBuffer(encodedCredentialId.slice(4)); + } + + return guidToRawFormat(encodedCredentialId); + } catch { + return undefined; + } +} + +/** + * Compares two credential IDs for equality. + */ +export function compareCredentialIds(a: Uint8Array, b: Uint8Array): boolean { + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +} diff --git a/libs/common/src/platform/services/fido2/domain-utils.ts b/libs/common/src/platform/services/fido2/domain-utils.ts index 6f11ddb1a75..67874355908 100644 --- a/libs/common/src/platform/services/fido2/domain-utils.ts +++ b/libs/common/src/platform/services/fido2/domain-utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { parse } from "tldts"; export function isValidRpId(rpId: string, origin: string) { diff --git a/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts b/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts index ce9426b4db0..8089e081805 100644 --- a/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts @@ -1,14 +1,18 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, Observable } from "rxjs"; -// FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths -import { flushPromises } from "@bitwarden/browser/src/autofill/spec/testing-utils"; - import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; import { Fido2ActiveRequestManager } from "./fido2-active-request-manager"; +// Duplicated from `apps/browser/src/autofill/spec/testing-utils.ts`. +const scheduler = typeof setImmediate === "function" ? setImmediate : setTimeout; +function flushPromises() { + return new Promise(function (resolve) { + scheduler(resolve); + }); +} + jest.mock("rxjs", () => { const rxjs = jest.requireActual("rxjs"); const { firstValueFrom } = rxjs; diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 5f15005d71c..226f4c2cfe9 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -26,9 +26,11 @@ import { import { Utils } from "../../misc/utils"; import { CBOR } from "./cbor"; +import { parseCredentialId } from "./credential-id-utils"; import { AAGUID, Fido2AuthenticatorService } from "./fido2-authenticator.service"; import { Fido2Utils } from "./fido2-utils"; -import { guidToRawFormat } from "./guid-utils"; + +type ParentWindowReference = string; const RpId = "bitwarden.com"; @@ -41,16 +43,16 @@ describe("FidoAuthenticatorService", () => { }); let cipherService!: MockProxy; - let userInterface!: MockProxy; + let userInterface!: MockProxy>; let userInterfaceSession!: MockProxy; let syncService!: MockProxy; let accountService!: MockProxy; - let authenticator!: Fido2AuthenticatorService; - let tab!: chrome.tabs.Tab; + let authenticator!: Fido2AuthenticatorService; + let windowReference!: ParentWindowReference; beforeEach(async () => { cipherService = mock(); - userInterface = mock(); + userInterface = mock>(); userInterfaceSession = mock(); userInterface.newSession.mockResolvedValue(userInterfaceSession); syncService = mock({ @@ -63,7 +65,7 @@ describe("FidoAuthenticatorService", () => { syncService, accountService, ); - tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; + windowReference = Utils.newGuid(); accountService.activeAccount$ = activeAccountSubject; }); @@ -78,19 +80,21 @@ describe("FidoAuthenticatorService", () => { // Spec: Check if at least one of the specified combinations of PublicKeyCredentialType and cryptographic parameters in credTypesAndPubKeyAlgs is supported. If not, return an error code equivalent to "NotSupportedError" and terminate the operation. it("should throw error when input does not contain any supported algorithms", async () => { const result = async () => - await authenticator.makeCredential(invalidParams.unsupportedAlgorithm, tab); + await authenticator.makeCredential(invalidParams.unsupportedAlgorithm, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotSupported); }); it("should throw error when requireResidentKey has invalid value", async () => { - const result = async () => await authenticator.makeCredential(invalidParams.invalidRk, tab); + const result = async () => + await authenticator.makeCredential(invalidParams.invalidRk, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); it("should throw error when requireUserVerification has invalid value", async () => { - const result = async () => await authenticator.makeCredential(invalidParams.invalidUv, tab); + const result = async () => + await authenticator.makeCredential(invalidParams.invalidUv, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); @@ -103,7 +107,7 @@ describe("FidoAuthenticatorService", () => { it.skip("should throw error if requireUserVerification is set to true", async () => { const params = await createParams({ requireUserVerification: true }); - const result = async () => await authenticator.makeCredential(params, tab); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Constraint); }); @@ -117,7 +121,7 @@ describe("FidoAuthenticatorService", () => { for (const p of Object.values(invalidParams)) { try { - await authenticator.makeCredential(p, tab); + await authenticator.makeCredential(p, windowReference); // eslint-disable-next-line no-empty } catch {} } @@ -139,7 +143,7 @@ describe("FidoAuthenticatorService", () => { params = await createParams({ excludeCredentialDescriptorList: [ { - id: guidToRawFormat(excludedCipher.login.fido2Credentials[0].credentialId), + id: parseCredentialId(excludedCipher.login.fido2Credentials[0].credentialId), type: "public-key", }, ], @@ -158,7 +162,7 @@ describe("FidoAuthenticatorService", () => { userInterfaceSession.informExcludedCredential.mockResolvedValue(); try { - await authenticator.makeCredential(params, tab); + await authenticator.makeCredential(params, windowReference); // eslint-disable-next-line no-empty } catch {} @@ -169,7 +173,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error", async () => { userInterfaceSession.informExcludedCredential.mockResolvedValue(); - const result = async () => await authenticator.makeCredential(params, tab); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); @@ -180,7 +184,7 @@ describe("FidoAuthenticatorService", () => { excludedCipher.organizationId = "someOrganizationId"; try { - await authenticator.makeCredential(params, tab); + await authenticator.makeCredential(params, windowReference); // eslint-disable-next-line no-empty } catch {} @@ -193,7 +197,7 @@ describe("FidoAuthenticatorService", () => { for (const p of Object.values(invalidParams)) { try { - await authenticator.makeCredential(p, tab); + await authenticator.makeCredential(p, windowReference); // eslint-disable-next-line no-empty } catch {} } @@ -230,7 +234,7 @@ describe("FidoAuthenticatorService", () => { userVerified: userVerification, }); - await authenticator.makeCredential(params, tab); + await authenticator.makeCredential(params, windowReference); expect(userInterfaceSession.confirmNewCredential).toHaveBeenCalledWith({ credentialName: params.rpEntity.name, @@ -250,7 +254,7 @@ describe("FidoAuthenticatorService", () => { }); cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as Cipher); - await authenticator.makeCredential(params, tab); + await authenticator.makeCredential(params, windowReference); const saved = cipherService.encrypt.mock.lastCall?.[0]; expect(saved).toEqual( @@ -288,7 +292,7 @@ describe("FidoAuthenticatorService", () => { }); const params = await createParams(); - const result = async () => await authenticator.makeCredential(params, tab); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); @@ -302,7 +306,7 @@ describe("FidoAuthenticatorService", () => { const encryptedCipher = { ...existingCipher, reprompt: CipherRepromptType.Password }; cipherService.get.mockResolvedValue(encryptedCipher as unknown as Cipher); - const result = async () => await authenticator.makeCredential(params, tab); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); @@ -317,7 +321,7 @@ describe("FidoAuthenticatorService", () => { cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as Cipher); cipherService.updateWithServer.mockRejectedValue(new Error("Internal error")); - const result = async () => await authenticator.makeCredential(params, tab); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); @@ -358,7 +362,7 @@ describe("FidoAuthenticatorService", () => { }); it("should return attestation object", async () => { - const result = await authenticator.makeCredential(params, tab); + const result = await authenticator.makeCredential(params, windowReference); const attestationObject = CBOR.decode( Fido2Utils.bufferSourceToUint8Array(result.attestationObject).buffer, @@ -455,7 +459,8 @@ describe("FidoAuthenticatorService", () => { describe("invalid input parameters", () => { it("should throw error when requireUserVerification has invalid value", async () => { - const result = async () => await authenticator.getAssertion(invalidParams.invalidUv, tab); + const result = async () => + await authenticator.getAssertion(invalidParams.invalidUv, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); @@ -468,7 +473,7 @@ describe("FidoAuthenticatorService", () => { it.skip("should throw error if requireUserVerification is set to true", async () => { const params = await createParams({ requireUserVerification: true }); - const result = async () => await authenticator.getAssertion(params, tab); + const result = async () => await authenticator.getAssertion(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Constraint); }); @@ -482,7 +487,7 @@ describe("FidoAuthenticatorService", () => { credentialId = Utils.newGuid(); params = await createParams({ allowCredentialDescriptorList: [ - { id: guidToRawFormat(credentialId), type: "public-key" }, + { id: parseCredentialId(credentialId), type: "public-key" }, ], rpId: RpId, }); @@ -498,7 +503,7 @@ describe("FidoAuthenticatorService", () => { userInterfaceSession.informCredentialNotFound.mockResolvedValue(); try { - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); // eslint-disable-next-line no-empty } catch {} @@ -513,7 +518,7 @@ describe("FidoAuthenticatorService", () => { userInterfaceSession.informCredentialNotFound.mockResolvedValue(); try { - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); // eslint-disable-next-line no-empty } catch {} @@ -534,7 +539,7 @@ describe("FidoAuthenticatorService", () => { /** Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. */ it("should throw error", async () => { - const result = async () => await authenticator.getAssertion(params, tab); + const result = async () => await authenticator.getAssertion(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); @@ -546,7 +551,7 @@ describe("FidoAuthenticatorService", () => { let params: Fido2AuthenticatorGetAssertionParams; beforeEach(async () => { - credentialIds = [Utils.newGuid(), Utils.newGuid()]; + credentialIds = [Utils.newGuid(), "b64.Lb5SVTumSV6gYJpeWh3laA"]; ciphers = [ await createCipherView( { type: CipherType.Login }, @@ -559,7 +564,7 @@ describe("FidoAuthenticatorService", () => { ]; params = await createParams({ allowCredentialDescriptorList: credentialIds.map((credentialId) => ({ - id: guidToRawFormat(credentialId), + id: parseCredentialId(credentialId), type: "public-key", })), rpId: RpId, @@ -573,7 +578,7 @@ describe("FidoAuthenticatorService", () => { userVerified: false, }); - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); expect(userInterfaceSession.pickCredential).toHaveBeenCalledWith({ cipherIds: ciphers.map((c) => c.id), @@ -590,7 +595,7 @@ describe("FidoAuthenticatorService", () => { userVerified: false, }); - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); expect(userInterfaceSession.pickCredential).toHaveBeenCalledWith({ cipherIds: [discoverableCiphers[0].id], @@ -608,7 +613,7 @@ describe("FidoAuthenticatorService", () => { userVerified: userVerification, }); - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); expect(userInterfaceSession.pickCredential).toHaveBeenCalledWith({ cipherIds: ciphers.map((c) => c.id), @@ -625,7 +630,7 @@ describe("FidoAuthenticatorService", () => { userVerified: false, }); - const result = async () => await authenticator.getAssertion(params, tab); + const result = async () => await authenticator.getAssertion(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); @@ -637,7 +642,7 @@ describe("FidoAuthenticatorService", () => { userVerified: false, }); - const result = async () => await authenticator.getAssertion(params, tab); + const result = async () => await authenticator.getAssertion(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.NotAllowed); }); @@ -667,7 +672,7 @@ describe("FidoAuthenticatorService", () => { selectedCredentialId = credentialIds[0]; params = await createParams({ allowCredentialDescriptorList: credentialIds.map((credentialId) => ({ - id: guidToRawFormat(credentialId), + id: parseCredentialId(credentialId), type: "public-key", })), rpId: RpId, @@ -686,7 +691,7 @@ describe("FidoAuthenticatorService", () => { cipherService.encrypt.mockResolvedValue(encrypted as any); ciphers[0].login.fido2Credentials[0].counter = 9000; - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); expect(cipherService.updateWithServer).toHaveBeenCalledWith(encrypted); expect(cipherService.encrypt).toHaveBeenCalledWith( @@ -710,20 +715,20 @@ describe("FidoAuthenticatorService", () => { cipherService.encrypt.mockResolvedValue(encrypted as any); ciphers[0].login.fido2Credentials[0].counter = 0; - await authenticator.getAssertion(params, tab); + await authenticator.getAssertion(params, windowReference); expect(cipherService.updateWithServer).not.toHaveBeenCalled(); }); it("should return an assertion result", async () => { - const result = await authenticator.getAssertion(params, tab); + const result = await authenticator.getAssertion(params, windowReference); const encAuthData = result.authenticatorData; const rpIdHash = encAuthData.slice(0, 32); const flags = encAuthData.slice(32, 33); const counter = encAuthData.slice(33, 37); - expect(result.selectedCredential.id).toEqual(guidToRawFormat(selectedCredentialId)); + expect(result.selectedCredential.id).toEqual(parseCredentialId(selectedCredentialId)); expect(result.selectedCredential.userHandle).toEqual( Fido2Utils.stringToBuffer(fido2Credentials[0].userHandle), ); @@ -757,7 +762,7 @@ describe("FidoAuthenticatorService", () => { for (let i = 0; i < 10; ++i) { await init(); // Reset inputs - const result = await authenticator.getAssertion(params, tab); + const result = await authenticator.getAssertion(params, windowReference); const counter = result.authenticatorData.slice(33, 37); expect(counter).toEqual(new Uint8Array([0, 0, 0x23, 0x29])); // double check that the counter doesn't change @@ -774,7 +779,7 @@ describe("FidoAuthenticatorService", () => { it("should throw unkown error if creation fails", async () => { cipherService.updateWithServer.mockRejectedValue(new Error("Internal error")); - const result = async () => await authenticator.getAssertion(params, tab); + const result = async () => await authenticator.getAssertion(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); }); diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 8f0523769d9..376f4dcdced 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { AccountService } from "../../../auth/abstractions/account.service"; @@ -23,9 +25,10 @@ import { LogService } from "../../abstractions/log.service"; import { Utils } from "../../misc/utils"; import { CBOR } from "./cbor"; +import { compareCredentialIds, parseCredentialId } from "./credential-id-utils"; import { p1363ToDer } from "./ecdsa-utils"; import { Fido2Utils } from "./fido2-utils"; -import { guidToRawFormat, guidToStandardFormat } from "./guid-utils"; +import { guidToStandardFormat } from "./guid-utils"; // AAGUID: d548826e-79b4-db40-a3d8-11116f7e8349 export const AAGUID = new Uint8Array([ @@ -40,10 +43,12 @@ const KeyUsages: KeyUsage[] = ["sign"]; * * It is highly recommended that the W3C specification is used a reference when reading this code. */ -export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstraction { +export class Fido2AuthenticatorService + implements Fido2AuthenticatorServiceAbstraction +{ constructor( private cipherService: CipherService, - private userInterface: Fido2UserInterfaceService, + private userInterface: Fido2UserInterfaceService, private syncService: SyncService, private accountService: AccountService, private logService?: LogService, @@ -51,12 +56,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr async makeCredential( params: Fido2AuthenticatorMakeCredentialsParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ): Promise { const userInterfaceSession = await this.userInterface.newSession( params.fallbackSupported, - tab, + window, abortController, ); @@ -178,7 +183,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr const authData = await generateAuthData({ rpId: params.rpEntity.id, - credentialId: guidToRawFormat(credentialId), + credentialId: parseCredentialId(credentialId), counter: fido2Credential.counter, userPresence: true, userVerification: userVerified, @@ -193,7 +198,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr ); return { - credentialId: guidToRawFormat(credentialId), + credentialId: parseCredentialId(credentialId), attestationObject, authData, publicKey: pubKeyDer, @@ -206,12 +211,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr async getAssertion( params: Fido2AuthenticatorGetAssertionParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController?: AbortController, ): Promise { const userInterfaceSession = await this.userInterface.newSession( params.fallbackSupported, - tab, + window, abortController, ); try { @@ -313,7 +318,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr const authenticatorData = await generateAuthData({ rpId: selectedFido2Credential.rpId, - credentialId: guidToRawFormat(selectedCredentialId), + credentialId: parseCredentialId(selectedCredentialId), counter: selectedFido2Credential.counter, userPresence: true, userVerification: userVerified, @@ -328,7 +333,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr return { authenticatorData, selectedCredential: { - id: guidToRawFormat(selectedCredentialId), + id: parseCredentialId(selectedCredentialId), userHandle: Fido2Utils.stringToBuffer(selectedFido2Credential.userHandle), }, signature, @@ -412,16 +417,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr credentials: PublicKeyCredentialDescriptor[], rpId: string, ): Promise { - const ids: string[] = []; - - for (const credential of credentials) { - try { - ids.push(guidToStandardFormat(credential.id)); - // eslint-disable-next-line no-empty - } catch {} - } - - if (ids.length === 0) { + if (credentials.length === 0) { return []; } @@ -432,7 +428,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr cipher.type === CipherType.Login && cipher.login.hasFido2Credentials && cipher.login.fido2Credentials[0].rpId === rpId && - ids.includes(cipher.login.fido2Credentials[0].credentialId), + credentials.some((credential) => + compareCredentialIds( + credential.id, + parseCredentialId(cipher.login.fido2Credentials[0].credentialId), + ), + ), ); } diff --git a/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts new file mode 100644 index 00000000000..31f6ce10e01 --- /dev/null +++ b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts @@ -0,0 +1,34 @@ +// TODO: Add tests for this method + +import { CipherType } from "../../../vault/enums"; +import { CipherView } from "../../../vault/models/view/cipher.view"; +import { Fido2CredentialAutofillView } from "../../../vault/models/view/fido2-credential-autofill.view"; +import { Utils } from "../../misc/utils"; + +import { parseCredentialId } from "./credential-id-utils"; + +// TODO: Move into Fido2AuthenticatorService +export async function getCredentialsForAutofill( + ciphers: CipherView[], +): Promise { + return ciphers + .filter( + (cipher) => + !cipher.isDeleted && cipher.type === CipherType.Login && cipher.login.hasFido2Credentials, + ) + .map((cipher) => { + const credential = cipher.login.fido2Credentials[0]; + + // Credentials are stored as a GUID or b64 string with `b64.` prepended, + // but we need to return them as a URL-safe base64 string + const credId = Utils.fromBufferToUrlB64(parseCredentialId(credential.credentialId)); + + return { + cipherId: cipher.id, + credentialId: credId, + rpId: credential.rpId, + userHandle: credential.userHandle, + userName: credential.userName, + }; + }); +} diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts index 582849ebc12..51c3d8617ab 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts @@ -32,12 +32,14 @@ import { Fido2ClientService } from "./fido2-client.service"; import { Fido2Utils } from "./fido2-utils"; import { guidToRawFormat } from "./guid-utils"; +type ParentWindowReference = string; + const RpId = "bitwarden.com"; const Origin = "https://bitwarden.com"; const VaultUrl = "https://vault.bitwarden.com"; describe("FidoAuthenticatorService", () => { - let authenticator!: MockProxy; + let authenticator!: MockProxy>; let configService!: MockProxy; let authService!: MockProxy; let vaultSettingsService: MockProxy; @@ -45,12 +47,12 @@ describe("FidoAuthenticatorService", () => { let taskSchedulerService: MockProxy; let activeRequest!: MockProxy; let requestManager!: MockProxy; - let client!: Fido2ClientService; - let tab!: chrome.tabs.Tab; + let client!: Fido2ClientService; + let windowReference!: ParentWindowReference; let isValidRpId!: jest.SpyInstance; beforeEach(async () => { - authenticator = mock(); + authenticator = mock>(); configService = mock(); authService = mock(); vaultSettingsService = mock(); @@ -82,7 +84,7 @@ describe("FidoAuthenticatorService", () => { vaultSettingsService.enablePasskeys$ = of(true); domainSettingsService.neverDomains$ = of({}); authService.activeAccountStatus$ = of(AuthenticationStatus.Unlocked); - tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; + windowReference = Utils.newGuid(); }); afterEach(() => { @@ -95,7 +97,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error if sameOriginWithAncestors is false", async () => { const params = createParams({ sameOriginWithAncestors: false }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "NotAllowedError" }); @@ -106,7 +108,7 @@ describe("FidoAuthenticatorService", () => { it("should throw error if user.id is too small", async () => { const params = createParams({ user: { id: "", displayName: "displayName", name: "name" } }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); await expect(result).rejects.toBeInstanceOf(TypeError); }); @@ -121,7 +123,7 @@ describe("FidoAuthenticatorService", () => { }, }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); await expect(result).rejects.toBeInstanceOf(TypeError); }); @@ -136,7 +138,7 @@ describe("FidoAuthenticatorService", () => { origin: "invalid-domain-name", }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -151,7 +153,7 @@ describe("FidoAuthenticatorService", () => { rp: { id: "bitwarden.com", name: "Bitwarden" }, }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -165,7 +167,7 @@ describe("FidoAuthenticatorService", () => { // `params` actually has a valid rp.id, but we're mocking the function to return false isValidRpId.mockReturnValue(false); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -179,7 +181,7 @@ describe("FidoAuthenticatorService", () => { }); domainSettingsService.neverDomains$ = of({ "bitwarden.com": null }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); await expect(result).rejects.toThrow(FallbackRequestedError); }); @@ -190,7 +192,7 @@ describe("FidoAuthenticatorService", () => { rp: { id: "bitwarden.com", name: "Bitwarden" }, }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -204,7 +206,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); - await client.createCredential(params, tab); + await client.createCredential(params, windowReference); }); // Spec: If credTypesAndPubKeyAlgs is empty, return a DOMException whose name is "NotSupportedError", and terminate this algorithm. @@ -216,7 +218,7 @@ describe("FidoAuthenticatorService", () => { ], }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "NotSupportedError" }); @@ -231,7 +233,8 @@ describe("FidoAuthenticatorService", () => { const abortController = new AbortController(); abortController.abort(); - const result = async () => await client.createCredential(params, tab, abortController); + const result = async () => + await client.createCredential(params, windowReference, abortController); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "AbortError" }); @@ -246,7 +249,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); - await client.createCredential(params, tab); + await client.createCredential(params, windowReference); expect(authenticator.makeCredential).toHaveBeenCalledWith( expect.objectContaining({ @@ -259,7 +262,7 @@ describe("FidoAuthenticatorService", () => { displayName: params.user.displayName, }), }), - tab, + windowReference, expect.anything(), ); }); @@ -271,7 +274,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); - const result = await client.createCredential(params, tab); + const result = await client.createCredential(params, windowReference); expect(result.extensions.credProps?.rk).toBe(true); }); @@ -283,7 +286,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); - const result = await client.createCredential(params, tab); + const result = await client.createCredential(params, windowReference); expect(result.extensions.credProps?.rk).toBe(false); }); @@ -295,7 +298,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult()); - const result = await client.createCredential(params, tab); + const result = await client.createCredential(params, windowReference); expect(result.extensions.credProps).toBeUndefined(); }); @@ -307,7 +310,7 @@ describe("FidoAuthenticatorService", () => { new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.InvalidState), ); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "InvalidStateError" }); @@ -319,7 +322,7 @@ describe("FidoAuthenticatorService", () => { const params = createParams(); authenticator.makeCredential.mockRejectedValue(new Error("unknown error")); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "NotAllowedError" }); @@ -330,7 +333,7 @@ describe("FidoAuthenticatorService", () => { const params = createParams(); vaultSettingsService.enablePasskeys$ = of(false); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); @@ -340,7 +343,7 @@ describe("FidoAuthenticatorService", () => { const params = createParams(); authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); @@ -349,7 +352,7 @@ describe("FidoAuthenticatorService", () => { it("should throw FallbackRequestedError if origin equals the bitwarden vault", async () => { const params = createParams({ origin: VaultUrl }); - const result = async () => await client.createCredential(params, tab); + const result = async () => await client.createCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); @@ -408,7 +411,7 @@ describe("FidoAuthenticatorService", () => { origin: "invalid-domain-name", }); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -423,7 +426,7 @@ describe("FidoAuthenticatorService", () => { rpId: "bitwarden.com", }); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -437,7 +440,7 @@ describe("FidoAuthenticatorService", () => { // `params` actually has a valid rp.id, but we're mocking the function to return false isValidRpId.mockReturnValue(false); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -451,7 +454,7 @@ describe("FidoAuthenticatorService", () => { domainSettingsService.neverDomains$ = of({ "bitwarden.com": null }); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); await expect(result).rejects.toThrow(FallbackRequestedError); }); @@ -462,7 +465,7 @@ describe("FidoAuthenticatorService", () => { rpId: "bitwarden.com", }); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "SecurityError" }); @@ -477,7 +480,8 @@ describe("FidoAuthenticatorService", () => { const abortController = new AbortController(); abortController.abort(); - const result = async () => await client.assertCredential(params, tab, abortController); + const result = async () => + await client.assertCredential(params, windowReference, abortController); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "AbortError" }); @@ -493,7 +497,7 @@ describe("FidoAuthenticatorService", () => { new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.InvalidState), ); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "InvalidStateError" }); @@ -505,7 +509,7 @@ describe("FidoAuthenticatorService", () => { const params = createParams(); authenticator.getAssertion.mockRejectedValue(new Error("unknown error")); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toMatchObject({ name: "NotAllowedError" }); @@ -516,7 +520,7 @@ describe("FidoAuthenticatorService", () => { const params = createParams(); vaultSettingsService.enablePasskeys$ = of(false); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); @@ -526,7 +530,7 @@ describe("FidoAuthenticatorService", () => { const params = createParams(); authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); @@ -535,7 +539,7 @@ describe("FidoAuthenticatorService", () => { it("should throw FallbackRequestedError if origin equals the bitwarden vault", async () => { const params = createParams({ origin: VaultUrl }); - const result = async () => await client.assertCredential(params, tab); + const result = async () => await client.assertCredential(params, windowReference); const rejects = expect(result).rejects; await rejects.toThrow(FallbackRequestedError); @@ -555,7 +559,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); - await client.assertCredential(params, tab); + await client.assertCredential(params, windowReference); expect(authenticator.getAssertion).toHaveBeenCalledWith( expect.objectContaining({ @@ -573,7 +577,7 @@ describe("FidoAuthenticatorService", () => { }), ], }), - tab, + windowReference, expect.anything(), ); }); @@ -585,7 +589,7 @@ describe("FidoAuthenticatorService", () => { params.rpId = undefined; authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); - await client.assertCredential(params, tab); + await client.assertCredential(params, windowReference); }); }); @@ -597,7 +601,7 @@ describe("FidoAuthenticatorService", () => { }); authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); - await client.assertCredential(params, tab); + await client.assertCredential(params, windowReference); expect(authenticator.getAssertion).toHaveBeenCalledWith( expect.objectContaining({ @@ -605,7 +609,7 @@ describe("FidoAuthenticatorService", () => { rpId: RpId, allowCredentialDescriptorList: [], }), - tab, + windowReference, expect.anything(), ); }); @@ -627,7 +631,7 @@ describe("FidoAuthenticatorService", () => { }); it("creates an active mediated conditional request", async () => { - await client.assertCredential(params, tab); + await client.assertCredential(params, windowReference); expect(requestManager.newActiveRequest).toHaveBeenCalled(); expect(authenticator.getAssertion).toHaveBeenCalledWith( @@ -635,14 +639,14 @@ describe("FidoAuthenticatorService", () => { assumeUserPresence: true, rpId: RpId, }), - tab, + windowReference, ); }); it("restarts the mediated conditional request if a user aborts the request", async () => { authenticator.getAssertion.mockRejectedValueOnce(new Error()); - await client.assertCredential(params, tab); + await client.assertCredential(params, windowReference); expect(authenticator.getAssertion).toHaveBeenCalledTimes(2); }); @@ -652,7 +656,7 @@ describe("FidoAuthenticatorService", () => { abortController.abort(); authenticator.getAssertion.mockRejectedValueOnce(new DOMException("AbortError")); - await client.assertCredential(params, tab); + await client.assertCredential(params, windowReference); expect(authenticator.getAssertion).toHaveBeenCalledTimes(2); }); diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index 849e0c72565..4bf30ef6537 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, Subscription } from "rxjs"; import { parse } from "tldts"; @@ -45,7 +47,9 @@ import { guidToRawFormat } from "./guid-utils"; * * It is highly recommended that the W3C specification is used a reference when reading this code. */ -export class Fido2ClientService implements Fido2ClientServiceAbstraction { +export class Fido2ClientService + implements Fido2ClientServiceAbstraction +{ private timeoutAbortController: AbortController; private readonly TIMEOUTS = { NO_VERIFICATION: { @@ -61,7 +65,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { }; constructor( - private authenticator: Fido2AuthenticatorService, + private authenticator: Fido2AuthenticatorService, private configService: ConfigService, private authService: AuthService, private vaultSettingsService: VaultSettingsService, @@ -100,7 +104,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { async createCredential( params: CreateCredentialParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController = new AbortController(), ): Promise { const parsedOrigin = parse(params.origin, { allowPrivateDomains: true }); @@ -199,7 +203,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { try { makeCredentialResult = await this.authenticator.makeCredential( makeCredentialParams, - tab, + window, abortController, ); } catch (error) { @@ -254,7 +258,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { async assertCredential( params: AssertCredentialParams, - tab: chrome.tabs.Tab, + window: ParentWindowReference, abortController = new AbortController(), ): Promise { const parsedOrigin = parse(params.origin, { allowPrivateDomains: true }); @@ -298,7 +302,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { if (params.mediation === "conditional") { return this.handleMediatedConditionalRequest( params, - tab, + window, abortController, clientDataJSONBytes, ); @@ -322,7 +326,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { try { getAssertionResult = await this.authenticator.getAssertion( getAssertionParams, - tab, + window, abortController, ); } catch (error) { @@ -361,7 +365,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { private async handleMediatedConditionalRequest( params: AssertCredentialParams, - tab: chrome.tabs.Tab, + tab: ParentWindowReference, abortController: AbortController, clientDataJSONBytes: Uint8Array, ): Promise { @@ -377,7 +381,10 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { `[Fido2Client] started mediated request, available credentials: ${availableCredentials.length}`, ); const requestResult = await this.requestManager.newActiveRequest( - tab.id, + // TODO: This isn't correct, but this.requestManager.newActiveRequest expects a number, + // while this class is currently generic over ParentWindowReference. + // Consider moving requestManager into browser and adding support for ParentWindowReference => tab.id + (tab as any).id, availableCredentials, abortController, ); diff --git a/libs/common/src/platform/services/fido2/fido2-utils.ts b/libs/common/src/platform/services/fido2/fido2-utils.ts index 58034912978..b9f3c8f8c48 100644 --- a/libs/common/src/platform/services/fido2/fido2-utils.ts +++ b/libs/common/src/platform/services/fido2/fido2-utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class Fido2Utils { static bufferToString(bufferSource: BufferSource): string { return Fido2Utils.fromBufferToB64(Fido2Utils.bufferSourceToUint8Array(bufferSource)) diff --git a/libs/common/src/platform/services/fido2/guid-utils.spec.ts b/libs/common/src/platform/services/fido2/guid-utils.spec.ts new file mode 100644 index 00000000000..098ea4bee75 --- /dev/null +++ b/libs/common/src/platform/services/fido2/guid-utils.spec.ts @@ -0,0 +1,28 @@ +import { guidToRawFormat } from "./guid-utils"; + +describe("guid-utils", () => { + describe("guidToRawFormat", () => { + it.each([ + [ + "00000000-0000-0000-0000-000000000000", + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ], + "08d70b74-e9f5-4522-a425-e5dcd40107e7", + [ + 0x08, 0xd7, 0x0b, 0x74, 0xe9, 0xf5, 0x45, 0x22, 0xa4, 0x25, 0xe5, 0xdc, 0xd4, 0x01, 0x07, + 0xe7, + ], + ], + ])("returns UUID in binary format when given a valid UUID string", (input, expected) => { + const result = guidToRawFormat(input); + + expect(result).toEqual(new Uint8Array(expected)); + }); + + it("throws an error when given an invalid UUID string", () => { + expect(() => guidToRawFormat("invalid")).toThrow(TypeError); + }); + }); +}); diff --git a/libs/common/src/platform/services/fido2/noop-fido2-user-interface.service.ts b/libs/common/src/platform/services/fido2/noop-fido2-user-interface.service.ts index 440bd519002..14b4da0ef1b 100644 --- a/libs/common/src/platform/services/fido2/noop-fido2-user-interface.service.ts +++ b/libs/common/src/platform/services/fido2/noop-fido2-user-interface.service.ts @@ -7,7 +7,7 @@ import { * Noop implementation of the {@link Fido2UserInterfaceService}. * This implementation does not provide any user interface. */ -export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { +export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { newSession(): Promise { throw new Error("Not implemented exception"); } diff --git a/libs/common/src/platform/services/file-upload/azure-file-upload.service.ts b/libs/common/src/platform/services/file-upload/azure-file-upload.service.ts index 74eaf45adf3..02adcfee22e 100644 --- a/libs/common/src/platform/services/file-upload/azure-file-upload.service.ts +++ b/libs/common/src/platform/services/file-upload/azure-file-upload.service.ts @@ -1,3 +1,6 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ApiService } from "../../../abstractions/api.service"; import { LogService } from "../../abstractions/log.service"; import { Utils } from "../../misc/utils"; import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; @@ -6,7 +9,10 @@ const MAX_SINGLE_BLOB_UPLOAD_SIZE = 256 * 1024 * 1024; // 256 MiB const MAX_BLOCKS_PER_BLOB = 50000; export class AzureFileUploadService { - constructor(private logService: LogService) {} + constructor( + private logService: LogService, + private apiService: ApiService, + ) {} async upload(url: string, data: EncArrayBuffer, renewalCallback: () => Promise) { if (data.buffer.byteLength <= MAX_SINGLE_BLOB_UPLOAD_SIZE) { @@ -31,7 +37,7 @@ export class AzureFileUploadService { headers: headers, }); - const blobResponse = await fetch(request); + const blobResponse = await this.apiService.nativeFetch(request); if (blobResponse.status !== 201) { throw new Error(`Failed to create Azure blob: ${blobResponse.status}`); @@ -77,7 +83,7 @@ export class AzureFileUploadService { headers: blockHeaders, }); - const blockResponse = await fetch(blockRequest); + const blockResponse = await this.apiService.nativeFetch(blockRequest); if (blockResponse.status !== 201) { const message = `Unsuccessful block PUT. Received status ${blockResponse.status}`; @@ -106,7 +112,7 @@ export class AzureFileUploadService { headers: headers, }); - const response = await fetch(request); + const response = await this.apiService.nativeFetch(request); if (response.status !== 201) { const message = `Unsuccessful block list PUT. Received status ${response.status}`; diff --git a/libs/common/src/platform/services/file-upload/bitwarden-file-upload.service.ts b/libs/common/src/platform/services/file-upload/bitwarden-file-upload.service.ts index 3d0c31ef3ed..93594405302 100644 --- a/libs/common/src/platform/services/file-upload/bitwarden-file-upload.service.ts +++ b/libs/common/src/platform/services/file-upload/bitwarden-file-upload.service.ts @@ -8,22 +8,21 @@ export class BitwardenFileUploadService { apiCall: (fd: FormData) => Promise, ) { const fd = new FormData(); - try { + + if (Utils.isBrowser) { const blob = new Blob([encryptedFileData.buffer], { type: "application/octet-stream" }); fd.append("data", blob, encryptedFileName); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append( - "data", - Buffer.from(encryptedFileData.buffer) as any, - { - filepath: encryptedFileName, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } + } else if (Utils.isNode) { + fd.append( + "data", + Buffer.from(encryptedFileData.buffer) as any, + { + filename: encryptedFileName, + contentType: "application/octet-stream", + } as any, + ); + } else { + throw new Error("Unsupported environment"); } await apiCall(fd); diff --git a/libs/common/src/platform/services/file-upload/file-upload.service.ts b/libs/common/src/platform/services/file-upload/file-upload.service.ts index 3ce10130d40..e09d8afac4e 100644 --- a/libs/common/src/platform/services/file-upload/file-upload.service.ts +++ b/libs/common/src/platform/services/file-upload/file-upload.service.ts @@ -1,3 +1,6 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ApiService } from "../../../abstractions/api.service"; import { FileUploadApiMethods, FileUploadService as FileUploadServiceAbstraction, @@ -14,8 +17,11 @@ export class FileUploadService implements FileUploadServiceAbstraction { private azureFileUploadService: AzureFileUploadService; private bitwardenFileUploadService: BitwardenFileUploadService; - constructor(protected logService: LogService) { - this.azureFileUploadService = new AzureFileUploadService(logService); + constructor( + protected logService: LogService, + apiService: ApiService, + ) { + this.azureFileUploadService = new AzureFileUploadService(logService, apiService); this.bitwardenFileUploadService = new BitwardenFileUploadService(); } diff --git a/libs/common/src/platform/services/i18n.service.ts b/libs/common/src/platform/services/i18n.service.ts index aa2692d20b5..87c9e211ed1 100644 --- a/libs/common/src/platform/services/i18n.service.ts +++ b/libs/common/src/platform/services/i18n.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, firstValueFrom, map } from "rxjs"; import { I18nService as I18nServiceAbstraction } from "../abstractions/i18n.service"; diff --git a/libs/common/src/platform/services/key-generation.service.spec.ts b/libs/common/src/platform/services/key-generation.service.spec.ts index 4f04eebd04e..7bc547dac17 100644 --- a/libs/common/src/platform/services/key-generation.service.spec.ts +++ b/libs/common/src/platform/services/key-generation.service.spec.ts @@ -1,6 +1,7 @@ import { mock } from "jest-mock-extended"; -import { Argon2KdfConfig, PBKDF2KdfConfig } from "../../auth/models/domain/kdf-config"; +import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management"; + import { CsprngArray } from "../../types/csprng"; import { CryptoFunctionService } from "../abstractions/crypto-function.service"; diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index b1c1ddfcf17..8b8ba6cb355 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,8 +1,10 @@ -import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig } from "../../auth/models/domain/kdf-config"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; + import { CsprngArray } from "../../types/csprng"; import { CryptoFunctionService } from "../abstractions/crypto-function.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "../abstractions/key-generation.service"; -import { KdfType } from "../enums"; import { Utils } from "../misc/utils"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/platform/services/memory-storage.service.ts b/libs/common/src/platform/services/memory-storage.service.ts index d5debf46cc4..52d38dec9bf 100644 --- a/libs/common/src/platform/services/memory-storage.service.ts +++ b/libs/common/src/platform/services/memory-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Subject } from "rxjs"; import { AbstractStorageService, StorageUpdate } from "../abstractions/storage.service"; diff --git a/libs/common/src/platform/services/migration-builder.service.ts b/libs/common/src/platform/services/migration-builder.service.ts index db08cadf4b0..879b49b9a53 100644 --- a/libs/common/src/platform/services/migration-builder.service.ts +++ b/libs/common/src/platform/services/migration-builder.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { createMigrationBuilder } from "../../state-migrations"; import { MigrationBuilder } from "../../state-migrations/migration-builder"; diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index ff82b3aa764..c5917e0230f 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -1,13 +1,10 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, of } from "rxjs"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { BitwardenClient } from "@bitwarden/sdk-internal"; -import { ApiService } from "../../../abstractions/api.service"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; -import { KdfConfigService } from "../../../auth/abstractions/kdf-config.service"; -import { PBKDF2KdfConfig } from "../../../auth/models/domain/kdf-config"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; @@ -26,7 +23,6 @@ describe("DefaultSdkService", () => { let accountService!: MockProxy; let kdfConfigService!: MockProxy; let keyService!: MockProxy; - let apiService!: MockProxy; let service!: DefaultSdkService; let mockClient!: MockProxy; @@ -38,7 +34,6 @@ describe("DefaultSdkService", () => { accountService = mock(); kdfConfigService = mock(); keyService = mock(); - apiService = mock(); // Can't use `of(mock())` for some reason environmentService.environment$ = new BehaviorSubject(mock()); @@ -50,7 +45,6 @@ describe("DefaultSdkService", () => { accountService, kdfConfigService, keyService, - apiService, ); mockClient = mock(); @@ -62,6 +56,9 @@ describe("DefaultSdkService", () => { const userId = "user-id" as UserId; beforeEach(() => { + environmentService.getEnvironment$ + .calledWith(userId) + .mockReturnValue(new BehaviorSubject(mock())); accountService.accounts$ = of({ [userId]: { email: "email", emailVerified: true, name: "name" } as AccountInfo, }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index b25ede006c5..e9cecbb15dc 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -1,16 +1,18 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatest, concatMap, - firstValueFrom, Observable, shareReplay, map, distinctUntilChanged, tap, switchMap, + catchError, } from "rxjs"; -import { KeyService } from "@bitwarden/key-management"; +import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; import { BitwardenClient, ClientSettings, @@ -18,11 +20,8 @@ import { DeviceType as SdkDeviceType, } from "@bitwarden/sdk-internal"; -import { ApiService } from "../../../abstractions/api.service"; import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; -import { KdfConfigService } from "../../../auth/abstractions/kdf-config.service"; -import { KdfConfig } from "../../../auth/models/domain/kdf-config"; import { DeviceType } from "../../../enums/device-type.enum"; import { OrganizationId, UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; @@ -30,7 +29,6 @@ import { Environment, EnvironmentService } from "../../abstractions/environment. import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkService } from "../../abstractions/sdk/sdk.service"; -import { KdfType } from "../../enums"; import { compareValues } from "../../misc/compare-values"; import { EncryptedString } from "../../models/domain/enc-string"; @@ -45,10 +43,9 @@ export class DefaultSdkService implements SdkService { shareReplay({ refCount: true, bufferSize: 1 }), ); - supported$ = this.client$.pipe( - concatMap(async (client) => { - return client.echo("bitwarden wasm!") === "bitwarden wasm!"; - }), + version$ = this.client$.pipe( + map((client) => client.version()), + catchError(() => "Unsupported"), ); constructor( @@ -58,7 +55,6 @@ export class DefaultSdkService implements SdkService { private accountService: AccountService, private kdfConfigService: KdfConfigService, private keyService: KeyService, - private apiService: ApiService, // Yes we shouldn't import ApiService, but it's temporary private userAgent: string = null, ) {} @@ -82,7 +78,7 @@ export class DefaultSdkService implements SdkService { ); const client$ = combineLatest([ - this.environmentService.environment$, + this.environmentService.getEnvironment$(userId), account$, kdfParams$, privateKey$, @@ -130,31 +126,6 @@ export class DefaultSdkService implements SdkService { return client$; } - async failedToInitialize(category: string, error?: Error): Promise { - // Only log on cloud instances - if ( - this.platformUtilsService.isDev() || - !(await firstValueFrom(this.environmentService.environment$)).isCloud - ) { - return; - } - - return this.apiService.send( - "POST", - "/wasm-debug", - { - category: category, - error: error?.message, - }, - false, - false, - null, - (headers) => { - headers.append("SDK-Version", "1.0.0"); - }, - ); - } - private async initializeClient( client: BitwardenClient, account: AccountInfo, diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index d46a5189a48..0ad2473d058 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { Jsonify, JsonValue } from "type-fest"; diff --git a/libs/common/src/platform/services/system.service.ts b/libs/common/src/platform/services/system.service.ts index 03e96af75b5..b12bfbd2754 100644 --- a/libs/common/src/platform/services/system.service.ts +++ b/libs/common/src/platform/services/system.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, Subscription } from "rxjs"; import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service"; diff --git a/libs/common/src/platform/services/translation.service.ts b/libs/common/src/platform/services/translation.service.ts index 4ad8162af57..ce7be58a186 100644 --- a/libs/common/src/platform/services/translation.service.ts +++ b/libs/common/src/platform/services/translation.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TranslationService as TranslationServiceAbstraction } from "../abstractions/translation.service"; export abstract class TranslationService implements TranslationServiceAbstraction { diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts index 23a8ba3138b..16b3968045a 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DefaultKeyService } from "../../../../key-management/src/key.service"; import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index abb8993c39c..a8947a49f45 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -1,3 +1,5 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { UserId } from "../../types/guid"; import { KeySuffixOptions } from "../enums"; diff --git a/libs/common/src/platform/services/web-crypto-function.service.spec.ts b/libs/common/src/platform/services/web-crypto-function.service.spec.ts index 71f2828855f..1929e6454ef 100644 --- a/libs/common/src/platform/services/web-crypto-function.service.spec.ts +++ b/libs/common/src/platform/services/web-crypto-function.service.spec.ts @@ -2,7 +2,7 @@ import { mock } from "jest-mock-extended"; import { Utils } from "../../platform/misc/utils"; import { PlatformUtilsService } from "../abstractions/platform-utils.service"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { WebCryptoFunctionService } from "./web-crypto-function.service"; @@ -253,8 +253,13 @@ describe("WebCrypto Function Service", () => { const encData = Utils.fromBufferToB64(encValue); const b64Iv = Utils.fromBufferToB64(iv); const symKey = new SymmetricCryptoKey(key); - const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = cryptoFunctionService.aesDecryptFastParameters( + encData, + b64Iv, + null, + symKey, + ); + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe(value); }); @@ -276,8 +281,8 @@ describe("WebCrypto Function Service", () => { const iv = Utils.fromBufferToB64(makeStaticByteArray(16)); const symKey = new SymmetricCryptoKey(makeStaticByteArray(32)); const data = "ByUF8vhyX4ddU9gcooznwA=="; - const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -287,10 +292,11 @@ describe("WebCrypto Function Service", () => { const cryptoFunctionService = getWebCryptoFunctionService(); const key = makeStaticByteArray(32); const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="); - const params = new DecryptParameters(); - params.encKey = Utils.fromBufferToByteString(key); - params.data = Utils.fromBufferToByteString(data); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + encKey: Utils.fromBufferToByteString(key), + data: Utils.fromBufferToByteString(data), + }; + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "ecb", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -304,6 +310,15 @@ describe("WebCrypto Function Service", () => { const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key, "cbc"); expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); }); + + it("throws if iv is not provided", async () => { + const cryptoFunctionService = getWebCryptoFunctionService(); + const key = makeStaticByteArray(32); + const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); + await expect(() => cryptoFunctionService.aesDecrypt(data, null, key, "cbc")).rejects.toThrow( + "IV is required for CBC mode", + ); + }); }); describe("aesDecrypt ECB mode", () => { diff --git a/libs/common/src/platform/services/web-crypto-function.service.ts b/libs/common/src/platform/services/web-crypto-function.service.ts index fd0763714ad..61edf7a13b1 100644 --- a/libs/common/src/platform/services/web-crypto-function.service.ts +++ b/libs/common/src/platform/services/web-crypto-function.service.ts @@ -4,7 +4,7 @@ import * as forge from "node-forge"; import { Utils } from "../../platform/misc/utils"; import { CsprngArray } from "../../types/csprng"; import { CryptoFunctionService } from "../abstractions/crypto-function.service"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export class WebCryptoFunctionService implements CryptoFunctionService { @@ -12,10 +12,14 @@ export class WebCryptoFunctionService implements CryptoFunctionService { private subtle: SubtleCrypto; private wasmSupported: boolean; - constructor(globalContext: Window | typeof global) { - this.crypto = typeof globalContext.crypto !== "undefined" ? globalContext.crypto : null; - this.subtle = - !!this.crypto && typeof this.crypto.subtle !== "undefined" ? this.crypto.subtle : null; + constructor(globalContext: { crypto: Crypto }) { + if (globalContext?.crypto?.subtle == null) { + throw new Error( + "Could not instantiate WebCryptoFunctionService. Could not locate Subtle crypto.", + ); + } + this.crypto = globalContext.crypto; + this.subtle = this.crypto.subtle; this.wasmSupported = this.checkIfWasmSupported(); } @@ -218,7 +222,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService { hmac.update(a); const mac1 = hmac.digest().getBytes(); - hmac.start(null, null); + hmac.start("sha256", null); hmac.update(b); const mac2 = hmac.digest().getBytes(); @@ -237,10 +241,10 @@ export class WebCryptoFunctionService implements CryptoFunctionService { aesDecryptFastParameters( data: string, iv: string, - mac: string, + mac: string | null, key: SymmetricCryptoKey, - ): DecryptParameters { - const p = new DecryptParameters(); + ): CbcDecryptParameters { + const p = {} as CbcDecryptParameters; if (key.meta != null) { p.encKey = key.meta.encKeyByteString; p.macKey = key.meta.macKeyByteString; @@ -273,7 +277,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService { return p; } - aesDecryptFast(parameters: DecryptParameters, mode: "cbc" | "ecb"): Promise { + aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise { const decipher = (forge as any).cipher.createDecipher( this.toWebCryptoAesMode(mode), parameters.encKey, @@ -292,21 +301,27 @@ export class WebCryptoFunctionService implements CryptoFunctionService { async aesDecrypt( data: Uint8Array, - iv: Uint8Array, + iv: Uint8Array | null, key: Uint8Array, mode: "cbc" | "ecb", ): Promise { if (mode === "ecb") { // Web crypto does not support AES-ECB mode, so we need to do this in forge. - const params = new DecryptParameters(); - params.data = this.toByteString(data); - params.encKey = this.toByteString(key); - const result = await this.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + data: this.toByteString(data), + encKey: this.toByteString(key), + }; + const result = await this.aesDecryptFast({ mode: "ecb", parameters }); return Utils.fromByteStringToArray(result); } const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [ "decrypt", ]); + + // CBC + if (iv == null) { + throw new Error("IV is required for CBC mode."); + } const buffer = await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data); return new Uint8Array(buffer); } diff --git a/libs/common/src/platform/state/derive-definition.ts b/libs/common/src/platform/state/derive-definition.ts index 826608b574c..663b55465b8 100644 --- a/libs/common/src/platform/state/derive-definition.ts +++ b/libs/common/src/platform/state/derive-definition.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/state/deserialization-helpers.ts b/libs/common/src/platform/state/deserialization-helpers.ts index 8fd5d2da198..cb7d393a83a 100644 --- a/libs/common/src/platform/state/deserialization-helpers.ts +++ b/libs/common/src/platform/state/deserialization-helpers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; /** diff --git a/libs/common/src/platform/state/implementations/default-active-user-state.provider.ts b/libs/common/src/platform/state/implementations/default-active-user-state.provider.ts index 9c81c69d152..91e6f37c418 100644 --- a/libs/common/src/platform/state/implementations/default-active-user-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-active-user-state.provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, distinctUntilChanged, map } from "rxjs"; import { AccountService } from "../../../auth/abstractions/account.service"; diff --git a/libs/common/src/platform/state/implementations/default-active-user-state.ts b/libs/common/src/platform/state/implementations/default-active-user-state.ts index ee90204b617..964b74f5378 100644 --- a/libs/common/src/platform/state/implementations/default-active-user-state.ts +++ b/libs/common/src/platform/state/implementations/default-active-user-state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, map, switchMap, firstValueFrom, timeout, throwError, NEVER } from "rxjs"; import { UserId } from "../../../types/guid"; diff --git a/libs/common/src/platform/state/implementations/default-global-state.provider.ts b/libs/common/src/platform/state/implementations/default-global-state.provider.ts index 03bca3f6d54..18e9c21e75e 100644 --- a/libs/common/src/platform/state/implementations/default-global-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-global-state.provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LogService } from "../../abstractions/log.service"; import { StorageServiceProvider } from "../../services/storage-service.provider"; import { GlobalState } from "../global-state"; diff --git a/libs/common/src/platform/state/implementations/default-single-user-state.provider.ts b/libs/common/src/platform/state/implementations/default-single-user-state.provider.ts index c63962f1cc7..54e268e0b69 100644 --- a/libs/common/src/platform/state/implementations/default-single-user-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-single-user-state.provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UserId } from "../../../types/guid"; import { LogService } from "../../abstractions/log.service"; import { StorageServiceProvider } from "../../services/storage-service.provider"; diff --git a/libs/common/src/platform/state/implementations/default-state.provider.ts b/libs/common/src/platform/state/implementations/default-state.provider.ts index 22aed80e8af..f86ba11b268 100644 --- a/libs/common/src/platform/state/implementations/default-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-state.provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, filter, of, switchMap, take } from "rxjs"; import { UserId } from "../../../types/guid"; diff --git a/libs/common/src/platform/state/implementations/state-base.ts b/libs/common/src/platform/state/implementations/state-base.ts index defd372eba6..567de957e53 100644 --- a/libs/common/src/platform/state/implementations/state-base.ts +++ b/libs/common/src/platform/state/implementations/state-base.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, ReplaySubject, diff --git a/libs/common/src/platform/state/key-definition.ts b/libs/common/src/platform/state/key-definition.ts index f5781fe2c16..a270fc3e1a2 100644 --- a/libs/common/src/platform/state/key-definition.ts +++ b/libs/common/src/platform/state/key-definition.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { StorageKey } from "../../types/state"; diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 47b7199b940..1ed5227cb13 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -121,6 +121,10 @@ export const TRANSLATION_DISK = new StateDefinition("translation", "disk", { web export const ANIMATION_DISK = new StateDefinition("animation", "disk"); export const TASK_SCHEDULER_DISK = new StateDefinition("taskScheduler", "disk"); +// Design System + +export const POPUP_STYLE_DISK = new StateDefinition("popupStyle", "disk"); + // Secrets Manager export const SM_ONBOARDING_DISK = new StateDefinition("smOnboarding", "disk", { @@ -146,6 +150,9 @@ export const COLLECTION_DATA = new StateDefinition("collection", "disk", { web: "memory", }); export const FOLDER_DISK = new StateDefinition("folder", "disk", { web: "memory" }); +export const FOLDER_MEMORY = new StateDefinition("decryptedFolders", "memory", { + browser: "memory-large-object", +}); export const VAULT_FILTER_DISK = new StateDefinition("vaultFilter", "disk", { web: "disk-local", }); @@ -173,3 +180,11 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro }); export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk"); export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk"); +export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( + "newDeviceVerificationNotice", + "disk", + { + web: "disk-local", + }, +); +export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); diff --git a/libs/common/src/platform/state/state-event-runner.service.ts b/libs/common/src/platform/state/state-event-runner.service.ts index 8fcc0710da9..f24c50f86d6 100644 --- a/libs/common/src/platform/state/state-event-runner.service.ts +++ b/libs/common/src/platform/state/state-event-runner.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/state/state-update-options.ts b/libs/common/src/platform/state/state-update-options.ts index 9073df33879..1a70dd2731e 100644 --- a/libs/common/src/platform/state/state-update-options.ts +++ b/libs/common/src/platform/state/state-update-options.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; export const DEFAULT_OPTIONS = { diff --git a/libs/common/src/platform/state/storage/memory-storage.service.ts b/libs/common/src/platform/state/storage/memory-storage.service.ts index ab45c101f90..df3fe615626 100644 --- a/libs/common/src/platform/state/storage/memory-storage.service.ts +++ b/libs/common/src/platform/state/storage/memory-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Subject } from "rxjs"; import { diff --git a/libs/common/src/platform/state/user-key-definition.ts b/libs/common/src/platform/state/user-key-definition.ts index 1548ab2a7c1..090a8aad31d 100644 --- a/libs/common/src/platform/state/user-key-definition.ts +++ b/libs/common/src/platform/state/user-key-definition.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UserId } from "../../types/guid"; import { StorageKey } from "../../types/state"; import { Utils } from "../misc/utils"; diff --git a/libs/common/src/platform/storage/window-storage.service.ts b/libs/common/src/platform/storage/window-storage.service.ts index 4eba94b2f2b..ed4189c39c9 100644 --- a/libs/common/src/platform/storage/window-storage.service.ts +++ b/libs/common/src/platform/storage/window-storage.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, Subject } from "rxjs"; import { diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 13f1525afd4..cfa9030c9de 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, Observable, of, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; @@ -83,18 +85,25 @@ export abstract class CoreSyncService implements SyncService { await this.stateProvider.getUser(userId, LAST_SYNC_DATE).update(() => date); } - async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { + async syncUpsertFolder( + notification: SyncFolderNotification, + isEdit: boolean, + userId: UserId, + ): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + + if (authStatus >= AuthenticationStatus.Locked) { try { - const localFolder = await this.folderService.get(notification.id); + const localFolder = await this.folderService.get(notification.id, userId); if ( (!isEdit && localFolder == null) || (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate) ) { const remoteFolder = await this.folderApiService.get(notification.id); if (remoteFolder != null) { - await this.folderService.upsert(new FolderData(remoteFolder)); + await this.folderService.upsert(new FolderData(remoteFolder), userId); this.messageSender.send("syncedUpsertedFolder", { folderId: notification.id }); return this.syncCompleted(true); } @@ -106,10 +115,13 @@ export abstract class CoreSyncService implements SyncService { return this.syncCompleted(false); } - async syncDeleteFolder(notification: SyncFolderNotification): Promise { + async syncDeleteFolder(notification: SyncFolderNotification, userId: UserId): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.folderService.delete(notification.id); + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + + if (authStatus >= AuthenticationStatus.Locked) { + await this.folderService.delete(notification.id, userId); this.messageSender.send("syncedDeletedFolder", { folderId: notification.id }); this.syncCompleted(true); return true; diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index eaf804d2866..138c7c03318 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { @@ -6,8 +8,14 @@ import { CollectionDetailsResponse, } from "@bitwarden/admin-console/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "../../../../auth/src/common/abstractions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "../../../../auth/src/common/types"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction"; diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts index 733b7beaff5..6763e01cab7 100644 --- a/libs/common/src/platform/sync/sync.service.ts +++ b/libs/common/src/platform/sync/sync.service.ts @@ -56,8 +56,9 @@ export abstract class SyncService { abstract syncUpsertFolder( notification: SyncFolderNotification, isEdit: boolean, + userId: UserId, ): Promise; - abstract syncDeleteFolder(notification: SyncFolderNotification): Promise; + abstract syncDeleteFolder(notification: SyncFolderNotification, userId: UserId): Promise; abstract syncUpsertCipher( notification: SyncCipherNotification, isEdit: boolean, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 0c508bfeb88..dc0a8d61f64 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { @@ -29,6 +31,7 @@ import { } from "../admin-console/models/response/organization-connection.response"; import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response"; import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response"; +import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response"; import { ProviderOrganizationOrganizationDetailsResponse, ProviderOrganizationResponse, @@ -41,7 +44,7 @@ import { } from "../admin-console/models/response/provider/provider-user.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; import { TokenService } from "../auth/abstractions/token.service"; -import { CreateAuthRequest } from "../auth/models/request/create-auth.request"; +import { AuthRequest } from "../auth/models/request/auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; @@ -259,11 +262,12 @@ export class ApiService implements ApiServiceAbstraction { } // TODO: PM-3519: Create and move to AuthRequest Api service - async postAuthRequest(request: CreateAuthRequest): Promise { + // TODO: PM-9724: Remove legacy auth request methods when we remove legacy LoginViaAuthRequestV1Components + async postAuthRequest(request: AuthRequest): Promise { const r = await this.send("POST", "/auth-requests/", request, false, true); return new AuthRequestResponse(r); } - async postAdminAuthRequest(request: CreateAuthRequest): Promise { + async postAdminAuthRequest(request: AuthRequest): Promise { const r = await this.send("POST", "/auth-requests/admin-request", request, true, true); return new AuthRequestResponse(r); } @@ -1680,8 +1684,10 @@ export class ApiService implements ApiServiceAbstraction { ); } - async postPreValidateSponsorshipToken(sponsorshipToken: string): Promise { - const r = await this.send( + async postPreValidateSponsorshipToken( + sponsorshipToken: string, + ): Promise { + const response = await this.send( "POST", "/organization/sponsorship/validate-token?sponsorshipToken=" + encodeURIComponent(sponsorshipToken), @@ -1689,7 +1695,8 @@ export class ApiService implements ApiServiceAbstraction { true, true, ); - return r as boolean; + + return new PreValidateSponsorshipResponse(response); } async postRedeemSponsorship( @@ -1941,7 +1948,6 @@ export class ApiService implements ApiServiceAbstraction { responseJson.error === "invalid_grant") ) { await this.logoutCallback("invalidGrantError"); - return null; } } diff --git a/libs/common/src/services/event/event-collection.service.ts b/libs/common/src/services/event/event-collection.service.ts index 570d84c6598..b06985e0ba7 100644 --- a/libs/common/src/services/event/event-collection.service.ts +++ b/libs/common/src/services/event/event-collection.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map, from, zip, Observable } from "rxjs"; import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service"; diff --git a/libs/common/src/services/event/event-upload.service.ts b/libs/common/src/services/event/event-upload.service.ts index faac95c4d6e..d3836a0030f 100644 --- a/libs/common/src/services/event/event-upload.service.ts +++ b/libs/common/src/services/event/event-upload.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts index d443193c9b7..f88c904bee1 100644 --- a/libs/common/src/services/notifications.service.ts +++ b/libs/common/src/services/notifications.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as signalR from "@microsoft/signalr"; import * as signalRMsgPack from "@microsoft/signalr-protocol-msgpack"; import { firstValueFrom, Subscription } from "rxjs"; @@ -166,10 +168,14 @@ export class NotificationsService implements NotificationsServiceAbstraction { await this.syncService.syncUpsertFolder( notification.payload as SyncFolderNotification, notification.type === NotificationType.SyncFolderUpdate, + payloadUserId, ); break; case NotificationType.SyncFolderDelete: - await this.syncService.syncDeleteFolder(notification.payload as SyncFolderNotification); + await this.syncService.syncDeleteFolder( + notification.payload as SyncFolderNotification, + payloadUserId, + ); break; case NotificationType.SyncVault: case NotificationType.SyncCiphers: @@ -216,6 +222,16 @@ export class NotificationsService implements NotificationsServiceAbstraction { }); } break; + case NotificationType.SyncOrganizationStatusChanged: + if (isAuthenticated) { + await this.syncService.fullSync(true); + } + break; + case NotificationType.SyncOrganizationCollectionSettingChanged: + if (isAuthenticated) { + await this.syncService.fullSync(true); + } + break; default: break; } diff --git a/libs/common/src/services/search.service.ts b/libs/common/src/services/search.service.ts index 38ddfe0e472..3ee46818432 100644 --- a/libs/common/src/services/search.service.ts +++ b/libs/common/src/services/search.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as lunr from "lunr"; import { Observable, firstValueFrom, map } from "rxjs"; import { Jsonify } from "type-fest"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts index 540f26bba2d..77ed6c960ab 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts @@ -6,10 +6,10 @@ import { FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith, FakeStateProvider } from "../../../spec"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; @@ -18,10 +18,12 @@ import { Policy } from "../../admin-console/models/domain/policy"; import { TokenService } from "../../auth/abstractions/token.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { LogService } from "../../platform/abstractions/log.service"; +import { Utils } from "../../platform/misc/utils"; import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION, } from "../../services/vault-timeout/vault-timeout-settings.state"; +import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts index a1bc93144b7..ffc8b6e0144 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EMPTY, Observable, @@ -19,6 +21,8 @@ import { } from "@bitwarden/auth/common"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts index 71341a98a62..986bbbf95a4 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts @@ -3,8 +3,7 @@ import { BehaviorSubject, from, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; +import { BiometricsService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { SearchService } from "../../abstractions/search.service"; @@ -14,10 +13,12 @@ import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { StateService } from "../../platform/abstractions/state.service"; import { Utils } from "../../platform/misc/utils"; +import { TaskSchedulerService } from "../../platform/scheduling"; import { StateEventRunnerService } from "../../platform/state"; import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; @@ -41,6 +42,7 @@ describe("VaultTimeoutService", () => { let stateEventRunnerService: MockProxy; let taskSchedulerService: MockProxy; let logService: MockProxy; + let biometricsService: MockProxy; let lockedCallback: jest.Mock, [userId: string]>; let loggedOutCallback: jest.Mock, [logoutReason: LogoutReason, userId?: string]>; @@ -66,6 +68,7 @@ describe("VaultTimeoutService", () => { stateEventRunnerService = mock(); taskSchedulerService = mock(); logService = mock(); + biometricsService = mock(); lockedCallback = jest.fn(); loggedOutCallback = jest.fn(); @@ -93,6 +96,7 @@ describe("VaultTimeoutService", () => { stateEventRunnerService, taskSchedulerService, logService, + biometricsService, lockedCallback, loggedOutCallback, ); @@ -334,7 +338,7 @@ describe("VaultTimeoutService", () => { // Active users should have additional steps ran expect(searchService.clearIndex).toHaveBeenCalled(); - expect(folderService.clearCache).toHaveBeenCalled(); + expect(folderService.clearDecryptedFolderState).toHaveBeenCalled(); expectUserToHaveLoggedOut("3"); // They have chosen logout as their action and it's available, log them out expectUserToHaveLoggedOut("4"); // They may have had lock as their chosen action but it's not available to them so logout diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index 07c3cd56f18..08dc02bb1ab 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -1,9 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; +import { BiometricsService } from "@bitwarden/key-management"; import { SearchService } from "../../abstractions/search.service"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; @@ -13,9 +14,11 @@ import { AuthService } from "../../auth/abstractions/auth.service"; import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { TaskSchedulerService, ScheduledTaskNames } from "../../platform/scheduling"; import { StateEventRunnerService } from "../../platform/state"; import { UserId } from "../../types/guid"; import { CipherService } from "../../vault/abstractions/cipher.service"; @@ -39,6 +42,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { private stateEventRunnerService: StateEventRunnerService, private taskSchedulerService: TaskSchedulerService, protected logService: LogService, + private biometricService: BiometricsService, private lockedCallback: (userId?: string) => Promise = null, private loggedOutCallback: ( logoutReason: LogoutReason, @@ -96,6 +100,8 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { } async lock(userId?: UserId): Promise { + await this.biometricService.setShouldAutopromptNow(false); + const authed = await this.stateService.getIsAuthenticated({ userId: userId }); if (!authed) { return; @@ -133,10 +139,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { if (userId == null || userId === currentUserId) { await this.searchService.clearIndex(); - await this.folderService.clearCache(); await this.collectionService.clearActiveUserCache(); } + await this.folderService.clearDecryptedFolderState(lockingUserId); await this.masterPasswordService.clearMasterKey(lockingUserId); await this.stateService.setUserKeyAutoUnlock(null, { userId: lockingUserId }); diff --git a/libs/common/src/state-migrations/migrations/10-move-ever-had-user-key-to-state-providers.ts b/libs/common/src/state-migrations/migrations/10-move-ever-had-user-key-to-state-providers.ts index 0315d5e26c6..53bf452b721 100644 --- a/libs/common/src/state-migrations/migrations/10-move-ever-had-user-key-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/10-move-ever-had-user-key-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/11-move-org-keys-to-state-providers.ts b/libs/common/src/state-migrations/migrations/11-move-org-keys-to-state-providers.ts index 147d4de458c..d805a29aad0 100644 --- a/libs/common/src/state-migrations/migrations/11-move-org-keys-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/11-move-org-keys-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/13-move-provider-keys-to-state-providers.ts b/libs/common/src/state-migrations/migrations/13-move-provider-keys-to-state-providers.ts index 623bbf60fb3..599e98804e5 100644 --- a/libs/common/src/state-migrations/migrations/13-move-provider-keys-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/13-move-provider-keys-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.ts index 1d310dc52dc..7c6d1c7da0b 100644 --- a/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/16-move-last-sync-to-state-provider.ts b/libs/common/src/state-migrations/migrations/16-move-last-sync-to-state-provider.ts index a7bac8d4daa..d3bd1e3acfe 100644 --- a/libs/common/src/state-migrations/migrations/16-move-last-sync-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/16-move-last-sync-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts index b70e89f9c0c..84bcaeec608 100644 --- a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts @@ -1,6 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, diff --git a/libs/common/src/state-migrations/migrations/20-move-private-key-to-state-providers.ts b/libs/common/src/state-migrations/migrations/20-move-private-key-to-state-providers.ts index 267a4e3aa03..74120428cda 100644 --- a/libs/common/src/state-migrations/migrations/20-move-private-key-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/20-move-private-key-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/21-move-collections-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/21-move-collections-state-to-state-provider.ts index 0192161f6a1..4add8240670 100644 --- a/libs/common/src/state-migrations/migrations/21-move-collections-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/21-move-collections-state-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/22-move-collapsed-groupings-to-state-provider.ts b/libs/common/src/state-migrations/migrations/22-move-collapsed-groupings-to-state-provider.ts index f8692ac3882..e0ecdbe6e7d 100644 --- a/libs/common/src/state-migrations/migrations/22-move-collapsed-groupings-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/22-move-collapsed-groupings-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts index 31c7bc25c78..9397191f54b 100644 --- a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts @@ -1,6 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ClearClipboardDelay = { Never: null as null, TenSeconds: 10, diff --git a/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts b/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts index ef9c1b37fa8..757730654bf 100644 --- a/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts index 376e5aefea9..8ce1d0b48e1 100644 --- a/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts index 3bb5874ee88..96f0a9acd5d 100644 --- a/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; diff --git a/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts index 5f05442c4e1..0b4c95cb55c 100644 --- a/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts index 1c5681f2aad..71e5c531d50 100644 --- a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts @@ -1,6 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const UriMatchStrategy = { Domain: 0, Host: 1, diff --git a/libs/common/src/state-migrations/migrations/36-move-show-card-and-identity-to-state-provider.ts b/libs/common/src/state-migrations/migrations/36-move-show-card-and-identity-to-state-provider.ts index 8e86507a3b8..5fb6654c422 100644 --- a/libs/common/src/state-migrations/migrations/36-move-show-card-and-identity-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/36-move-show-card-and-identity-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/37-move-avatar-color-to-state-providers.ts b/libs/common/src/state-migrations/migrations/37-move-avatar-color-to-state-providers.ts index 36173cc909c..c71059d9aa2 100644 --- a/libs/common/src/state-migrations/migrations/37-move-avatar-color-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/37-move-avatar-color-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/38-migrate-token-svc-to-state-provider.ts b/libs/common/src/state-migrations/migrations/38-migrate-token-svc-to-state-provider.ts index 640e63cdc54..6bafdf3db1a 100644 --- a/libs/common/src/state-migrations/migrations/38-migrate-token-svc-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/38-migrate-token-svc-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts index 1dfb019942a..862fe33bd4e 100644 --- a/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; diff --git a/libs/common/src/state-migrations/migrations/41-move-event-collection-to-state-provider.ts b/libs/common/src/state-migrations/migrations/41-move-event-collection-to-state-provider.ts index f2f4d94d195..ff8d00fd079 100644 --- a/libs/common/src/state-migrations/migrations/41-move-event-collection-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/41-move-event-collection-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/43-move-auto-confirm-finger-prints-to-state-provider.ts b/libs/common/src/state-migrations/migrations/43-move-auto-confirm-finger-prints-to-state-provider.ts index 246e3cf4365..a211d9830fb 100644 --- a/libs/common/src/state-migrations/migrations/43-move-auto-confirm-finger-prints-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/43-move-auto-confirm-finger-prints-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/44-move-user-decryption-options-to-state-provider.ts b/libs/common/src/state-migrations/migrations/44-move-user-decryption-options-to-state-provider.ts index 708b096280c..55543e62c81 100644 --- a/libs/common/src/state-migrations/migrations/44-move-user-decryption-options-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/44-move-user-decryption-options-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts b/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts index 2eb11f06987..21071db7abb 100644 --- a/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts +++ b/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MigrationHelper } from "../migration-helper"; import { Direction, Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/50-move-key-connector-to-state-provider.ts b/libs/common/src/state-migrations/migrations/50-move-key-connector-to-state-provider.ts index 0deb7d5e2c0..d71cddd25ce 100644 --- a/libs/common/src/state-migrations/migrations/50-move-key-connector-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/50-move-key-connector-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts b/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts index b6d2c19b156..cb943d03fbe 100644 --- a/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts b/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts index 7f60d18ffe9..7d3a2bebbf9 100644 --- a/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts +++ b/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/55-move-master-key-state-to-provider.ts b/libs/common/src/state-migrations/migrations/55-move-master-key-state-to-provider.ts index 99b22b56617..c9b0eac99b5 100644 --- a/libs/common/src/state-migrations/migrations/55-move-master-key-state-to-provider.ts +++ b/libs/common/src/state-migrations/migrations/55-move-master-key-state-to-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/56-move-auth-requests.ts b/libs/common/src/state-migrations/migrations/56-move-auth-requests.ts index 4fec3b2de03..a37335b7be3 100644 --- a/libs/common/src/state-migrations/migrations/56-move-auth-requests.ts +++ b/libs/common/src/state-migrations/migrations/56-move-auth-requests.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts b/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts index 332306c6d4b..81ee193867b 100644 --- a/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/61-move-pin-state-to-providers.ts b/libs/common/src/state-migrations/migrations/61-move-pin-state-to-providers.ts index e587bcb6eef..317c671e2c8 100644 --- a/libs/common/src/state-migrations/migrations/61-move-pin-state-to-providers.ts +++ b/libs/common/src/state-migrations/migrations/61-move-pin-state-to-providers.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts b/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts index 7451fd37514..5874a80dc60 100644 --- a/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/64-migrate-generator-history.ts b/libs/common/src/state-migrations/migrations/64-migrate-generator-history.ts index 3ca4c643184..9aaf4c90078 100644 --- a/libs/common/src/state-migrations/migrations/64-migrate-generator-history.ts +++ b/libs/common/src/state-migrations/migrations/64-migrate-generator-history.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/65-migrate-forwarder-settings.ts b/libs/common/src/state-migrations/migrations/65-migrate-forwarder-settings.ts index 6dad7ae3420..0dbb848e424 100644 --- a/libs/common/src/state-migrations/migrations/65-migrate-forwarder-settings.ts +++ b/libs/common/src/state-migrations/migrations/65-migrate-forwarder-settings.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/66-move-final-desktop-settings.ts b/libs/common/src/state-migrations/migrations/66-move-final-desktop-settings.ts index 726a8a060bf..c13014cd83f 100644 --- a/libs/common/src/state-migrations/migrations/66-move-final-desktop-settings.ts +++ b/libs/common/src/state-migrations/migrations/66-move-final-desktop-settings.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/68-move-last-sync-date.ts b/libs/common/src/state-migrations/migrations/68-move-last-sync-date.ts index b8957b0d241..eb4c83ae693 100644 --- a/libs/common/src/state-migrations/migrations/68-move-last-sync-date.ts +++ b/libs/common/src/state-migrations/migrations/68-move-last-sync-date.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/state-migrations/migrations/8-move-state-version.ts b/libs/common/src/state-migrations/migrations/8-move-state-version.ts index cbcdf423843..db3be04b7c6 100644 --- a/libs/common/src/state-migrations/migrations/8-move-state-version.ts +++ b/libs/common/src/state-migrations/migrations/8-move-state-version.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { JsonObject } from "type-fest"; import { MigrationHelper } from "../migration-helper"; diff --git a/libs/common/src/state-migrations/migrations/9-move-browser-settings-to-global.ts b/libs/common/src/state-migrations/migrations/9-move-browser-settings-to-global.ts index f18bba55536..8f1eea61e84 100644 --- a/libs/common/src/state-migrations/migrations/9-move-browser-settings-to-global.ts +++ b/libs/common/src/state-migrations/migrations/9-move-browser-settings-to-global.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts new file mode 100644 index 00000000000..831cad74155 --- /dev/null +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -0,0 +1,492 @@ +import { mock } from "jest-mock-extended"; +import { BehaviorSubject, Subject } from "rxjs"; + +import { KeyService } from "@bitwarden/key-management"; + +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../types/csprng"; +import { OrganizationId, UserId } from "../../types/guid"; +import { OrgKey, UserKey } from "../../types/key"; +import { OrganizationBound, UserBound } from "../dependencies"; + +import { KeyServiceLegacyEncryptorProvider } from "./key-service-legacy-encryptor-provider"; +import { OrganizationEncryptor } from "./organization-encryptor.abstraction"; +import { OrganizationKeyEncryptor } from "./organization-key-encryptor"; +import { UserEncryptor } from "./user-encryptor.abstraction"; +import { UserKeyEncryptor } from "./user-key-encryptor"; + +const encryptService = mock(); +const keyService = mock(); + +const SomeCsprngArray = new Uint8Array(64) as CsprngArray; +const SomeUser = "some user" as UserId; +const AnotherUser = "another user" as UserId; +const SomeUserKey = new SymmetricCryptoKey(SomeCsprngArray) as UserKey; +const SomeOrganization = "some organization" as OrganizationId; +const AnotherOrganization = "another organization" as OrganizationId; +const SomeOrgKey = new SymmetricCryptoKey(SomeCsprngArray) as OrgKey; +const AnotherOrgKey = new SymmetricCryptoKey(SomeCsprngArray) as OrgKey; +const OrgRecords: Record = { + [SomeOrganization]: SomeOrgKey, + [AnotherOrganization]: AnotherOrgKey, +}; + +// Many tests examine the private members of the objects constructed by the +// provider. This is necessary because it's not presently possible to spy +// on the constructors directly. +describe("KeyServiceLegacyEncryptorProvider", () => { + describe("userEncryptor$", () => { + it("emits a user key encryptor bound to the user", async () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: UserBound<"encryptor", UserEncryptor>[] = []; + + provider.userEncryptor$(1, { singleUserId$ }).subscribe((v) => results.push(v)); + + expect(keyService.userKey$).toHaveBeenCalledWith(SomeUser); + expect(results.length).toBe(1); + expect(results[0]).toMatchObject({ + userId: SomeUser, + encryptor: { + userId: SomeUser, + key: SomeUserKey, + dataPacker: { frameSize: 1 }, + }, + }); + expect(results[0].encryptor).toBeInstanceOf(UserKeyEncryptor); + }); + + it("waits until `dependencies.singleUserId$` emits", () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new Subject(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: UserBound<"encryptor", UserEncryptor>[] = []; + provider.userEncryptor$(1, { singleUserId$ }).subscribe((v) => results.push(v)); + // precondition: no emissions occur on subscribe + expect(results.length).toBe(0); + + singleUserId$.next(SomeUser); + + expect(results.length).toBe(1); + }); + + it("emits a new user key encryptor each time `dependencies.singleUserId$` emits", () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new Subject(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: UserBound<"encryptor", UserEncryptor>[] = []; + provider.userEncryptor$(1, { singleUserId$ }).subscribe((v) => results.push(v)); + + singleUserId$.next(SomeUser); + singleUserId$.next(SomeUser); + + expect(results.length).toBe(2); + expect(results[0]).not.toBe(results[1]); + }); + + it("waits until `userKey$` emits a truthy value", () => { + const userKey$ = new BehaviorSubject(null); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: UserBound<"encryptor", UserEncryptor>[] = []; + provider.userEncryptor$(1, { singleUserId$ }).subscribe((v) => results.push(v)); + // precondition: no emissions occur on subscribe + expect(results.length).toBe(0); + + userKey$.next(SomeUserKey); + + expect(results.length).toBe(1); + expect(results[0]).toMatchObject({ + userId: SomeUser, + encryptor: { + userId: SomeUser, + key: SomeUserKey, + dataPacker: { frameSize: 1 }, + }, + }); + }); + + it("emits a user key encryptor each time `userKey$` emits", () => { + const userKey$ = new Subject(); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: UserBound<"encryptor", UserEncryptor>[] = []; + provider.userEncryptor$(1, { singleUserId$ }).subscribe((v) => results.push(v)); + + userKey$.next(SomeUserKey); + userKey$.next(SomeUserKey); + + expect(results.length).toBe(2); + }); + + it("errors when the userId changes", () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new Subject(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .userEncryptor$(1, { singleUserId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + singleUserId$.next(SomeUser); + singleUserId$.next(AnotherUser); + + expect(error).toEqual({ expectedUserId: SomeUser, actualUserId: AnotherUser }); + }); + + it("errors when `dependencies.singleUserId$` errors", () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new Subject(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .userEncryptor$(1, { singleUserId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + singleUserId$.error({ some: "error" }); + + expect(error).toEqual({ some: "error" }); + }); + + it("errors once `dependencies.singleUserId$` emits and `userKey$` errors", () => { + const userKey$ = new Subject(); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .userEncryptor$(1, { singleUserId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + userKey$.error({ some: "error" }); + + expect(error).toEqual({ some: "error" }); + }); + + it("completes when `dependencies.singleUserId$` completes", () => { + const userKey$ = new Subject(); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let completed = false; + provider + .userEncryptor$(1, { singleUserId$ }) + .subscribe({ complete: () => (completed = true) }); + + singleUserId$.complete(); + + expect(completed).toBeTrue(); + }); + + it("completes when `userKey$` emits a falsy value after emitting a truthy value", () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let completed = false; + provider + .userEncryptor$(1, { singleUserId$ }) + .subscribe({ complete: () => (completed = true) }); + + userKey$.next(null); + + expect(completed).toBeTrue(); + }); + + it("completes once `dependencies.singleUserId$` emits and `userKey$` completes", () => { + const userKey$ = new BehaviorSubject(SomeUserKey); + keyService.userKey$.mockReturnValue(userKey$); + const singleUserId$ = new BehaviorSubject(SomeUser); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let completed = false; + provider + .userEncryptor$(1, { singleUserId$ }) + .subscribe({ complete: () => (completed = true) }); + + userKey$.complete(); + + expect(completed).toBeTrue(); + }); + }); + + describe("organizationEncryptor$", () => { + it("emits an organization key encryptor bound to the organization", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: OrganizationBound<"encryptor", OrganizationEncryptor>[] = []; + + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe((v) => results.push(v)); + + expect(keyService.orgKeys$).toHaveBeenCalledWith(SomeUser); + expect(results.length).toBe(1); + expect(results[0]).toMatchObject({ + organizationId: SomeOrganization, + encryptor: { + organizationId: SomeOrganization, + key: SomeOrgKey, + dataPacker: { frameSize: 1 }, + }, + }); + expect(results[0].encryptor).toBeInstanceOf(OrganizationKeyEncryptor); + }); + + it("waits until `dependencies.singleOrganizationId$` emits", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new Subject>(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: OrganizationBound<"encryptor", OrganizationEncryptor>[] = []; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe((v) => results.push(v)); + // precondition: no emissions occur on subscribe + expect(results.length).toBe(0); + + singleOrganizationId$.next({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + + expect(results.length).toBe(1); + }); + + it("emits a new organization key encryptor when `dependencies.singleOrganizationId$` emits", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new Subject>(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: OrganizationBound<"encryptor", OrganizationEncryptor>[] = []; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe((v) => results.push(v)); + // precondition: no emissions occur on subscribe + expect(results.length).toBe(0); + + singleOrganizationId$.next({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + singleOrganizationId$.next({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + + expect(results.length).toBe(2); + expect(results[0]).not.toBe(results[1]); + }); + + it("waits until `orgKeys$` emits a truthy value", () => { + const orgKey$ = new BehaviorSubject>(null); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: OrganizationBound<"encryptor", OrganizationEncryptor>[] = []; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe((v) => results.push(v)); + // precondition: no emissions occur on subscribe + expect(results.length).toBe(0); + + orgKey$.next(OrgRecords); + + expect(results.length).toBe(1); + expect(results[0]).toMatchObject({ + organizationId: SomeOrganization, + encryptor: { + organizationId: SomeOrganization, + key: SomeOrgKey, + dataPacker: { frameSize: 1 }, + }, + }); + }); + + it("emits an organization key encryptor each time `orgKeys$` emits", () => { + const orgKey$ = new Subject>(); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + const results: OrganizationBound<"encryptor", OrganizationEncryptor>[] = []; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe((v) => results.push(v)); + + orgKey$.next(OrgRecords); + orgKey$.next(OrgRecords); + + expect(results.length).toBe(2); + }); + + it("errors when the userId changes", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new Subject>(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + singleOrganizationId$.next({ userId: SomeUser, organizationId: SomeOrganization }); + singleOrganizationId$.next({ userId: AnotherUser, organizationId: SomeOrganization }); + + expect(error).toEqual({ expectedUserId: SomeUser, actualUserId: AnotherUser }); + }); + + it("errors when the organizationId changes", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new Subject>(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + singleOrganizationId$.next({ userId: SomeUser, organizationId: SomeOrganization }); + singleOrganizationId$.next({ userId: SomeUser, organizationId: AnotherOrganization }); + + expect(error).toEqual({ + expectedOrganizationId: SomeOrganization, + actualOrganizationId: AnotherOrganization, + }); + }); + + it("errors when `dependencies.singleOrganizationId$` errors", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new Subject>(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + singleOrganizationId$.error({ some: "error" }); + + expect(error).toEqual({ some: "error" }); + }); + + it("errors once `dependencies.singleOrganizationId$` emits and `orgKeys$` errors", () => { + const orgKey$ = new Subject>(); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + orgKey$.error({ some: "error" }); + + expect(error).toEqual({ some: "error" }); + }); + + it("errors when the user lacks the requested org key", () => { + const orgKey$ = new BehaviorSubject>({}); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let error: unknown = false; + + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ error: (e: unknown) => (error = e) }); + + expect(error).toBeInstanceOf(Error); + }); + + it("completes when `dependencies.singleOrganizationId$` completes", () => { + const orgKey$ = new BehaviorSubject(OrgRecords); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new Subject>(); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let completed = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ complete: () => (completed = true) }); + + singleOrganizationId$.complete(); + + expect(completed).toBeTrue(); + }); + + it("completes when `orgKeys$` emits a falsy value after emitting a truthy value", () => { + const orgKey$ = new Subject>(); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let completed = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ complete: () => (completed = true) }); + + orgKey$.next(OrgRecords); + orgKey$.next(null); + + expect(completed).toBeTrue(); + }); + + it("completes once `dependencies.singleOrganizationId$` emits and `userKey$` completes", () => { + const orgKey$ = new Subject>(); + keyService.orgKeys$.mockReturnValue(orgKey$); + const singleOrganizationId$ = new BehaviorSubject< + UserBound<"organizationId", OrganizationId> + >({ + organizationId: SomeOrganization, + userId: SomeUser, + }); + const provider = new KeyServiceLegacyEncryptorProvider(encryptService, keyService); + let completed = false; + provider + .organizationEncryptor$(1, { singleOrganizationId$ }) + .subscribe({ complete: () => (completed = true) }); + + orgKey$.complete(); + + expect(completed).toBeTrue(); + }); + }); +}); diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts new file mode 100644 index 00000000000..d4a8dec7dc3 --- /dev/null +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts @@ -0,0 +1,134 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { + connect, + dematerialize, + map, + materialize, + ReplaySubject, + skipWhile, + switchMap, + takeUntil, + takeWhile, +} from "rxjs"; + +import { KeyService } from "@bitwarden/key-management"; + +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { OrganizationId, UserId } from "../../types/guid"; +import { + OrganizationBound, + SingleOrganizationDependency, + SingleUserDependency, + UserBound, +} from "../dependencies"; +import { anyComplete, errorOnChange } from "../rx"; +import { PaddedDataPacker } from "../state/padded-data-packer"; + +import { LegacyEncryptorProvider } from "./legacy-encryptor-provider"; +import { OrganizationEncryptor } from "./organization-encryptor.abstraction"; +import { OrganizationKeyEncryptor } from "./organization-key-encryptor"; +import { UserEncryptor } from "./user-encryptor.abstraction"; +import { UserKeyEncryptor } from "./user-key-encryptor"; + +/** Creates encryptors + */ +export class KeyServiceLegacyEncryptorProvider implements LegacyEncryptorProvider { + /** Instantiates the legacy encryptor provider. + * @param encryptService injected into encryptors to perform encryption + * @param keyService looks up keys for construction into an encryptor + */ + constructor( + private readonly encryptService: EncryptService, + private readonly keyService: KeyService, + ) {} + + userEncryptor$(frameSize: number, dependencies: SingleUserDependency) { + const packer = new PaddedDataPacker(frameSize); + const encryptor$ = dependencies.singleUserId$.pipe( + errorOnChange( + (userId) => userId, + (expectedUserId, actualUserId) => ({ expectedUserId, actualUserId }), + ), + connect((singleUserId$) => { + const singleUserId = new ReplaySubject(1); + singleUserId$.subscribe(singleUserId); + + return singleUserId.pipe( + switchMap((userId) => + this.keyService.userKey$(userId).pipe( + // wait until the key becomes available + skipWhile((key) => !key), + // complete when the key becomes unavailable + takeWhile((key) => !!key), + map((key) => { + const encryptor = new UserKeyEncryptor(userId, this.encryptService, key, packer); + + return { userId, encryptor } satisfies UserBound<"encryptor", UserEncryptor>; + }), + materialize(), + ), + ), + dematerialize(), + takeUntil(anyComplete(singleUserId)), + ); + }), + ); + + return encryptor$; + } + + organizationEncryptor$(frameSize: number, dependencies: SingleOrganizationDependency) { + const packer = new PaddedDataPacker(frameSize); + const encryptor$ = dependencies.singleOrganizationId$.pipe( + errorOnChange( + (pair) => pair.userId, + (expectedUserId, actualUserId) => ({ expectedUserId, actualUserId }), + ), + errorOnChange( + (pair) => pair.organizationId, + (expectedOrganizationId, actualOrganizationId) => ({ + expectedOrganizationId, + actualOrganizationId, + }), + ), + connect((singleOrganizationId$) => { + const singleOrganizationId = new ReplaySubject>( + 1, + ); + singleOrganizationId$.subscribe(singleOrganizationId); + + return singleOrganizationId.pipe( + switchMap((pair) => + this.keyService.orgKeys$(pair.userId).pipe( + // wait until the key becomes available + skipWhile((keys) => !keys), + // complete when the key becomes unavailable + takeWhile((keys) => !!keys), + map((keys) => { + const organizationId = pair.organizationId; + const key = keys[organizationId]; + const encryptor = new OrganizationKeyEncryptor( + organizationId, + this.encryptService, + key, + packer, + ); + + return { organizationId, encryptor } satisfies OrganizationBound< + "encryptor", + OrganizationEncryptor + >; + }), + materialize(), + ), + ), + dematerialize(), + takeUntil(anyComplete(singleOrganizationId)), + ); + }), + ); + + return encryptor$; + } +} diff --git a/libs/common/src/tools/cryptography/legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/legacy-encryptor-provider.ts new file mode 100644 index 00000000000..3e5a8aa06bb --- /dev/null +++ b/libs/common/src/tools/cryptography/legacy-encryptor-provider.ts @@ -0,0 +1,44 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Observable } from "rxjs"; + +import { + OrganizationBound, + SingleOrganizationDependency, + SingleUserDependency, + UserBound, +} from "../dependencies"; + +import { OrganizationEncryptor } from "./organization-encryptor.abstraction"; +import { UserEncryptor } from "./user-encryptor.abstraction"; + +/** Creates encryptors + * @deprecated this logic will soon be replaced with a design that provides for + * key rotation. Use it at your own risk + */ +export abstract class LegacyEncryptorProvider { + /** Retrieves an encryptor populated with the user's most recent key instance that + * uses a padded data packer to encode data. + * @param frameSize length of the padded data packer's frames. + * @param dependencies.singleUserId$ identifies the user to which the encryptor is bound + * @returns an observable that emits when the key becomes available and completes + * when the key becomes unavailable. + */ + userEncryptor$: ( + frameSize: number, + dependencies: SingleUserDependency, + ) => Observable>; + + /** Retrieves an encryptor populated with the organization's most recent key instance that + * uses a padded data packer to encode data. + * @param frameSize length of the padded data packer's frames. + * @param dependencies.singleOrganizationId$ identifies the user/org combination + * to which the encryptor is bound. + * @returns an observable that emits when the key becomes available and completes + * when the key becomes unavailable. + */ + organizationEncryptor$: ( + frameSize: number, + dependences: SingleOrganizationDependency, + ) => Observable>; +} diff --git a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts new file mode 100644 index 00000000000..9eec207e92d --- /dev/null +++ b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts @@ -0,0 +1,34 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Jsonify } from "type-fest"; + +import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; + +/** An encryption strategy that protects a type's secrets with + * organization-specific keys. This strategy is bound to a specific organization. + */ +export abstract class OrganizationEncryptor { + /** Identifies the organization bound to the encryptor. */ + readonly organizationId: OrganizationId; + + /** Protects secrets in `value` with an organization-specific key. + * @param secret the object to protect. This object is mutated during encryption. + * @returns a promise that resolves to a tuple. The tuple's first property contains + * the encrypted secret and whose second property contains an object w/ disclosed + * properties. + * @throws If `value` is `null` or `undefined`, the promise rejects with an error. + */ + abstract encrypt(secret: Jsonify): Promise; + + /** Combines protected secrets and disclosed data into a type that can be + * rehydrated into a domain object. + * @param secret an encrypted JSON payload containing encrypted secrets. + * @returns a promise that resolves to the raw state. This state *is not* a + * class. It contains only data that can be round-tripped through JSON, + * and lacks members such as a prototype or bound functions. + * @throws If `secret` or `disclosed` is `null` or `undefined`, the promise + * rejects with an error. + */ + abstract decrypt(secret: EncString): Promise>; +} diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts new file mode 100644 index 00000000000..62c8ea24ae6 --- /dev/null +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts @@ -0,0 +1,125 @@ +import { mock } from "jest-mock-extended"; + +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncString } from "../../platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../types/csprng"; +import { OrganizationId } from "../../types/guid"; +import { OrgKey } from "../../types/key"; +import { DataPacker } from "../state/data-packer.abstraction"; + +import { OrganizationKeyEncryptor } from "./organization-key-encryptor"; + +describe("OrgKeyEncryptor", () => { + const encryptService = mock(); + const dataPacker = mock(); + const orgKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as OrgKey; + const anyOrgId = "foo" as OrganizationId; + + beforeEach(() => { + // The OrgKeyEncryptor is, in large part, a facade coordinating a handful of worker + // objects, so its tests focus on how data flows between components. The defaults rely + // on this property--that the facade treats its data like a opaque objects--to trace + // the data through several function calls. Should the encryptor interact with the + // objects themselves, these mocks will break. + encryptService.encrypt.mockImplementation((p) => Promise.resolve(p as unknown as EncString)); + encryptService.decryptToUtf8.mockImplementation((c) => Promise.resolve(c as unknown as string)); + dataPacker.pack.mockImplementation((v) => v as string); + dataPacker.unpack.mockImplementation((v: string) => v as T); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("constructor", () => { + it("should set organizationId", async () => { + const encryptor = new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, dataPacker); + expect(encryptor.organizationId).toEqual(anyOrgId); + }); + + it("should throw if organizationId was not supplied", async () => { + expect(() => new OrganizationKeyEncryptor(null, encryptService, orgKey, dataPacker)).toThrow( + "organizationId cannot be null or undefined", + ); + expect(() => new OrganizationKeyEncryptor(null, encryptService, orgKey, dataPacker)).toThrow( + "organizationId cannot be null or undefined", + ); + }); + + it("should throw if encryptService was not supplied", async () => { + expect(() => new OrganizationKeyEncryptor(anyOrgId, null, orgKey, dataPacker)).toThrow( + "encryptService cannot be null or undefined", + ); + expect(() => new OrganizationKeyEncryptor(anyOrgId, null, orgKey, dataPacker)).toThrow( + "encryptService cannot be null or undefined", + ); + }); + + it("should throw if key was not supplied", async () => { + expect( + () => new OrganizationKeyEncryptor(anyOrgId, encryptService, null, dataPacker), + ).toThrow("key cannot be null or undefined"); + expect( + () => new OrganizationKeyEncryptor(anyOrgId, encryptService, null, dataPacker), + ).toThrow("key cannot be null or undefined"); + }); + + it("should throw if dataPacker was not supplied", async () => { + expect(() => new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, null)).toThrow( + "dataPacker cannot be null or undefined", + ); + expect(() => new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, null)).toThrow( + "dataPacker cannot be null or undefined", + ); + }); + }); + + describe("encrypt", () => { + it("should throw if value was not supplied", async () => { + const encryptor = new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, dataPacker); + + await expect(encryptor.encrypt>(null)).rejects.toThrow( + "secret cannot be null or undefined", + ); + await expect(encryptor.encrypt>(undefined)).rejects.toThrow( + "secret cannot be null or undefined", + ); + }); + + it("should encrypt a packed value using the organization's key", async () => { + const encryptor = new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, dataPacker); + const value = { foo: true }; + + const result = await encryptor.encrypt(value); + + // these are data flow expectations; the operations all all pass-through mocks + expect(dataPacker.pack).toHaveBeenCalledWith(value); + expect(encryptService.encrypt).toHaveBeenCalledWith(value, orgKey); + expect(result).toBe(value); + }); + }); + + describe("decrypt", () => { + it("should throw if secret was not supplied", async () => { + const encryptor = new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, dataPacker); + + await expect(encryptor.decrypt(null)).rejects.toThrow("secret cannot be null or undefined"); + await expect(encryptor.decrypt(undefined)).rejects.toThrow( + "secret cannot be null or undefined", + ); + }); + + it("should declassify a decrypted packed value using the organization's key", async () => { + const encryptor = new OrganizationKeyEncryptor(anyOrgId, encryptService, orgKey, dataPacker); + const secret = "encrypted" as any; + + const result = await encryptor.decrypt(secret); + + // these are data flow expectations; the operations all all pass-through mocks + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(secret, orgKey); + expect(dataPacker.unpack).toHaveBeenCalledWith(secret); + expect(result).toBe(secret); + }); + }); +}); diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.ts new file mode 100644 index 00000000000..d3b7dae10f5 --- /dev/null +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.ts @@ -0,0 +1,61 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Jsonify } from "type-fest"; + +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; +import { OrgKey } from "../../types/key"; +import { DataPacker } from "../state/data-packer.abstraction"; + +import { OrganizationEncryptor } from "./organization-encryptor.abstraction"; + +/** A classification strategy that protects a type's secrets by encrypting them + * with an `OrgKey` + */ +export class OrganizationKeyEncryptor extends OrganizationEncryptor { + /** Instantiates the encryptor + * @param organizationId identifies the organization bound to the encryptor. + * @param encryptService protects properties of `Secret`. + * @param key the key instance protecting the data. + * @param dataPacker packs and unpacks data classified as secrets. + */ + constructor( + readonly organizationId: OrganizationId, + private readonly encryptService: EncryptService, + private readonly key: OrgKey, + private readonly dataPacker: DataPacker, + ) { + super(); + this.assertHasValue("organizationId", organizationId); + this.assertHasValue("key", key); + this.assertHasValue("dataPacker", dataPacker); + this.assertHasValue("encryptService", encryptService); + } + + async encrypt(secret: Jsonify): Promise { + this.assertHasValue("secret", secret); + + let packed = this.dataPacker.pack(secret); + const encrypted = await this.encryptService.encrypt(packed, this.key); + packed = null; + + return encrypted; + } + + async decrypt(secret: EncString): Promise> { + this.assertHasValue("secret", secret); + + let decrypted = await this.encryptService.decryptToUtf8(secret, this.key); + const unpacked = this.dataPacker.unpack(decrypted); + decrypted = null; + + return unpacked; + } + + private assertHasValue(name: string, value: any) { + if (value === undefined || value === null) { + throw new Error(`${name} cannot be null or undefined`); + } + } +} diff --git a/libs/common/src/tools/state/user-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts similarity index 91% rename from libs/common/src/tools/state/user-encryptor.abstraction.ts rename to libs/common/src/tools/cryptography/user-encryptor.abstraction.ts index 70b2fa82b8b..6bb0e252af3 100644 --- a/libs/common/src/tools/state/user-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts @@ -1,8 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * user-specific keys. This strategy is bound to a specific user. diff --git a/libs/common/src/tools/state/user-key-encryptor.spec.ts b/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts similarity index 98% rename from libs/common/src/tools/state/user-key-encryptor.spec.ts rename to libs/common/src/tools/cryptography/user-key-encryptor.spec.ts index 37c11554881..5b0ee5103cb 100644 --- a/libs/common/src/tools/state/user-key-encryptor.spec.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts @@ -6,8 +6,8 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; import { UserKey } from "../../types/key"; +import { DataPacker } from "../state/data-packer.abstraction"; -import { DataPacker } from "./data-packer.abstraction"; import { UserKeyEncryptor } from "./user-key-encryptor"; describe("UserKeyEncryptor", () => { diff --git a/libs/common/src/tools/state/user-key-encryptor.ts b/libs/common/src/tools/cryptography/user-key-encryptor.ts similarity index 90% rename from libs/common/src/tools/state/user-key-encryptor.ts rename to libs/common/src/tools/cryptography/user-key-encryptor.ts index d0316636d26..296c33ea1dc 100644 --- a/libs/common/src/tools/state/user-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.ts @@ -1,12 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; import { UserKey } from "../../types/key"; +import { DataPacker } from "../state/data-packer.abstraction"; -import { DataPacker } from "./data-packer.abstraction"; import { UserEncryptor } from "./user-encryptor.abstraction"; /** A classification strategy that protects a type's secrets by encrypting them diff --git a/libs/common/src/tools/dependencies.ts b/libs/common/src/tools/dependencies.ts index 84e2f53fa29..c22e71cff67 100644 --- a/libs/common/src/tools/dependencies.ts +++ b/libs/common/src/tools/dependencies.ts @@ -1,9 +1,10 @@ import { Observable } from "rxjs"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { UserId } from "@bitwarden/common/types/guid"; +import { Policy } from "../admin-console/models/domain/policy"; +import { OrganizationId, UserId } from "../types/guid"; -import { UserEncryptor } from "./state/user-encryptor.abstraction"; +import { OrganizationEncryptor } from "./cryptography/organization-encryptor.abstraction"; +import { UserEncryptor } from "./cryptography/user-encryptor.abstraction"; /** error emitted when the `SingleUserDependency` changes Ids */ export type UserChangedError = { @@ -13,6 +14,14 @@ export type UserChangedError = { actualUserId: UserId; }; +/** error emitted when the `SingleOrganizationDependency` changes Ids */ +export type OrganizationChangedError = { + /** the organizationId pinned by the single organization dependency */ + expectedOrganizationId: OrganizationId; + /** the organizationId received in error */ + actualOrganizationId: OrganizationId; +}; + /** A pattern for types that depend upon a dynamic policy stream and return * an observable. * @@ -55,6 +64,54 @@ export type UserBound = { [P in K]: T } & { userId: UserId; }; +/** Decorates a type to indicate the organization, if any, that the type is usable only by + * a specific organization. + */ +export type OrganizationBound = { [P in K]: T } & { + /** The organization to which T is bound. */ + organizationId: OrganizationId; +}; + +/** A pattern for types that depend upon a fixed-key encryptor and return + * an observable. + * + * Consumers of this dependency should emit a `OrganizationChangedError` if + * the bound OrganizationId changes or if the encryptor changes. If + * `singleOrganizationEncryptor$` completes, the consumer should complete + * once all events received prior to the completion event are + * finished processing. The consumer should, where possible, + * prioritize these events in order to complete as soon as possible. + * If `singleOrganizationEncryptor$` emits an unrecoverable error, the consumer + * should also emit the error. + */ +export type SingleOrganizationEncryptorDependency = { + /** A stream that emits an encryptor when subscribed and the org key + * is available, and completes when the org key is no longer available. + * The stream should not emit null or undefined. + */ + singleOrgEncryptor$: Observable>; +}; + +/** A pattern for types that depend upon a fixed-value organizationId and return + * an observable. + * + * Consumers of this dependency should emit a `OrganizationChangedError` if + * the value of `singleOrganizationId$` changes. If `singleOrganizationId$` completes, + * the consumer should also complete. If `singleOrganizationId$` errors, the + * consumer should also emit the error. + * + * @remarks Check the consumer's documentation to determine how it + * responds to repeat emissions. + */ +export type SingleOrganizationDependency = { + /** A stream that emits an organization Id and the user to which it is bound + * when subscribed and the user's account is unlocked, and completes when the + * account is locked or logged out. + * The stream should not emit null or undefined. + */ + singleOrganizationId$: Observable>; +}; + /** A pattern for types that depend upon a fixed-key encryptor and return * an observable. * diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts index 58115c783c7..42581c08dee 100644 --- a/libs/common/src/tools/integration/integration-context.spec.ts +++ b/libs/common/src/tools/integration/integration-context.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nService } from "../../platform/abstractions/i18n.service"; import { IntegrationContext } from "./integration-context"; import { IntegrationId } from "./integration-id"; diff --git a/libs/common/src/tools/integration/integration-context.ts b/libs/common/src/tools/integration/integration-context.ts index 3debf54ba36..40648df6803 100644 --- a/libs/common/src/tools/integration/integration-context.ts +++ b/libs/common/src/tools/integration/integration-context.ts @@ -1,5 +1,7 @@ -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { I18nService } from "../../platform/abstractions/i18n.service"; +import { Utils } from "../../platform/misc/utils"; import { IntegrationMetadata } from "./integration-metadata"; import { ApiSettings, IntegrationRequest } from "./rpc"; diff --git a/libs/common/src/tools/integration/rpc/rest-client.spec.ts b/libs/common/src/tools/integration/rpc/rest-client.spec.ts index e113ab9ff43..9b76d305e6f 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.spec.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { RestClient } from "./rest-client"; diff --git a/libs/common/src/tools/integration/rpc/rest-client.ts b/libs/common/src/tools/integration/rpc/rest-client.ts index c9096d0ce1d..c42244166e7 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.ts @@ -1,5 +1,7 @@ -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { JsonRpc } from "./rpc"; diff --git a/libs/common/src/tools/password-strength/password-strength.service.abstraction.ts b/libs/common/src/tools/password-strength/password-strength.service.abstraction.ts index 5708a4c7bd1..a49a6d481b5 100644 --- a/libs/common/src/tools/password-strength/password-strength.service.abstraction.ts +++ b/libs/common/src/tools/password-strength/password-strength.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ZXCVBNResult } from "zxcvbn"; export abstract class PasswordStrengthServiceAbstraction { diff --git a/libs/common/src/tools/password-strength/password-strength.service.ts b/libs/common/src/tools/password-strength/password-strength.service.ts index b367f7deafe..77854943acd 100644 --- a/libs/common/src/tools/password-strength/password-strength.service.ts +++ b/libs/common/src/tools/password-strength/password-strength.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as zxcvbn from "zxcvbn"; import { PasswordStrengthServiceAbstraction } from "./password-strength.service.abstraction"; diff --git a/libs/common/src/tools/private-classifier.ts b/libs/common/src/tools/private-classifier.ts index f9648504b76..58244ae9906 100644 --- a/libs/common/src/tools/private-classifier.ts +++ b/libs/common/src/tools/private-classifier.ts @@ -1,6 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PrivateClassifier implements Classifier, Data> { constructor(private keys: (keyof Jsonify)[] = undefined) {} diff --git a/libs/common/src/tools/public-classifier.ts b/libs/common/src/tools/public-classifier.ts index 82396f1c169..e036ebd1c42 100644 --- a/libs/common/src/tools/public-classifier.ts +++ b/libs/common/src/tools/public-classifier.ts @@ -1,6 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PublicClassifier implements Classifier> { constructor(private keys: (keyof Jsonify)[]) {} diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index f6932f01dc1..9ce147a3ff4 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -8,6 +8,7 @@ import { awaitAsync, trackEmissions } from "../../spec"; import { anyComplete, + errorOnChange, distinctIfShallowMatch, on, ready, @@ -15,6 +16,104 @@ import { withLatestReady, } from "./rx"; +describe("errorOnChange", () => { + it("emits a single value when the input emits only once", async () => { + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); + + source$.next(1); + + expect(results).toEqual([1]); + }); + + it("emits when the input emits", async () => { + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); + + source$.next(1); + source$.next(1); + + expect(results).toEqual([1, 1]); + }); + + it("errors when the input errors", async () => { + const source$ = new Subject(); + const expected = {}; + let error: any = null; + source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); + + source$.error(expected); + + expect(error).toBe(expected); + }); + + it("completes when the input completes", async () => { + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(errorOnChange()).subscribe({ complete: () => (complete = true) }); + + source$.complete(); + + expect(complete).toBeTrue(); + }); + + it("errors when the input changes", async () => { + const source$ = new Subject(); + let error: any = null; + source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); + + source$.next(1); + source$.next(2); + + expect(error).toEqual({ expectedValue: 1, actualValue: 2 }); + }); + + it("emits when the extracted value remains constant", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + const results: Foo[] = []; + source$.pipe(errorOnChange((v) => v.foo)).subscribe((v) => results.push(v)); + + source$.next({ foo: "bar" }); + source$.next({ foo: "bar" }); + + expect(results).toEqual([{ foo: "bar" }, { foo: "bar" }]); + }); + + it("errors when an extracted value changes", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + let error: any = null; + source$.pipe(errorOnChange((v) => v.foo)).subscribe({ error: (v: unknown) => (error = v) }); + + source$.next({ foo: "bar" }); + source$.next({ foo: "baz" }); + + expect(error).toEqual({ expectedValue: "bar", actualValue: "baz" }); + }); + + it("constructs an error when the extracted value changes", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + let error: any = null; + source$ + .pipe( + errorOnChange( + (v) => v.foo, + (expected, actual) => ({ expected, actual }), + ), + ) + .subscribe({ error: (v: unknown) => (error = v) }); + + source$.next({ foo: "bar" }); + source$.next({ foo: "baz" }); + + expect(error).toEqual({ expected: "bar", actual: "baz" }); + }); +}); + describe("reduceCollection", () => { it.each([[null], [undefined], [[]]])( "should return the default value when the collection is %p", diff --git a/libs/common/src/tools/rx.ts b/libs/common/src/tools/rx.ts index d5d0b499ff2..bff28ee52f8 100644 --- a/libs/common/src/tools/rx.ts +++ b/libs/common/src/tools/rx.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, distinctUntilChanged, @@ -15,8 +17,60 @@ import { takeUntil, withLatestFrom, concatMap, + startWith, + pairwise, } from "rxjs"; +/** Returns its input. */ +function identity(value: any): any { + return value; +} + +/** Combines its arguments into a plain old javascript object. */ +function expectedAndActualValue(expectedValue: any, actualValue: any) { + return { + expectedValue, + actualValue, + }; +} + +/** + * An observable operator that throws an error when the stream's + * value changes. Uses strict (`===`) comparison checks. + * @param extract a function that identifies the member to compare; + * defaults to the identity function + * @param error a function that packages the expected and failed + * values into an error. + * @returns a stream of values that emits when the input emits, + * completes when the input completes, and errors when either the + * input errors or the comparison fails. + */ +export function errorOnChange( + extract: (value: Input) => Extracted = identity, + error: (expectedValue: Extracted, actualValue: Extracted) => unknown = expectedAndActualValue, +): OperatorFunction { + return pipe( + startWith(null), + pairwise(), + map(([expected, actual], i) => { + // always let the first value through + if (i === 0) { + return actual; + } + + const expectedValue = extract(expected); + const actualValue = extract(actual); + + // fail the stream if the state desyncs from its initial value + if (expectedValue === actualValue) { + return actual; + } else { + throw error(expectedValue, actualValue); + } + }), + ); +} + /** * An observable operator that reduces an emitted collection to a single object, * returning a default if all items are ignored. diff --git a/libs/common/src/tools/send/models/api/send-file.api.ts b/libs/common/src/tools/send/models/api/send-file.api.ts index 87ac476b485..e0eb50daaa1 100644 --- a/libs/common/src/tools/send/models/api/send-file.api.ts +++ b/libs/common/src/tools/send/models/api/send-file.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; export class SendFileApi extends BaseResponse { diff --git a/libs/common/src/tools/send/models/api/send-text.api.ts b/libs/common/src/tools/send/models/api/send-text.api.ts index ee318f29d84..732efb01064 100644 --- a/libs/common/src/tools/send/models/api/send-text.api.ts +++ b/libs/common/src/tools/send/models/api/send-text.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; export class SendTextApi extends BaseResponse { diff --git a/libs/common/src/tools/send/models/data/send-file.data.ts b/libs/common/src/tools/send/models/data/send-file.data.ts index 5cdc9b19bb8..627b6dee4e0 100644 --- a/libs/common/src/tools/send/models/data/send-file.data.ts +++ b/libs/common/src/tools/send/models/data/send-file.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendFileApi } from "../api/send-file.api"; export class SendFileData { diff --git a/libs/common/src/tools/send/models/data/send-text.data.ts b/libs/common/src/tools/send/models/data/send-text.data.ts index 8e7335cde51..4b346e5f163 100644 --- a/libs/common/src/tools/send/models/data/send-text.data.ts +++ b/libs/common/src/tools/send/models/data/send-text.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendTextApi } from "../api/send-text.api"; export class SendTextData { diff --git a/libs/common/src/tools/send/models/data/send.data.ts b/libs/common/src/tools/send/models/data/send.data.ts index 7cba7303396..e4df5e48dce 100644 --- a/libs/common/src/tools/send/models/data/send.data.ts +++ b/libs/common/src/tools/send/models/data/send.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendType } from "../../enums/send-type"; import { SendResponse } from "../response/send.response"; diff --git a/libs/common/src/tools/send/models/domain/send-access.ts b/libs/common/src/tools/send/models/domain/send-access.ts index 5c225432b72..dcc2d3ef426 100644 --- a/libs/common/src/tools/send/models/domain/send-access.ts +++ b/libs/common/src/tools/send/models/domain/send-access.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import Domain from "../../../../platform/models/domain/domain-base"; import { EncString } from "../../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/tools/send/models/domain/send-file.ts b/libs/common/src/tools/send/models/domain/send-file.ts index 33856876775..90e40f3959a 100644 --- a/libs/common/src/tools/send/models/domain/send-file.ts +++ b/libs/common/src/tools/send/models/domain/send-file.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../../platform/models/domain/domain-base"; diff --git a/libs/common/src/tools/send/models/domain/send-text.ts b/libs/common/src/tools/send/models/domain/send-text.ts index 26862d3db79..b17e3f769fb 100644 --- a/libs/common/src/tools/send/models/domain/send-text.ts +++ b/libs/common/src/tools/send/models/domain/send-text.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../../platform/models/domain/domain-base"; diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 74c0e77b394..fcc273d41bb 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,12 +1,12 @@ import { mock } from "jest-mock-extended"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; import { EncryptService } from "../../../../platform/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../../platform/services/container.service"; +import { UserKey } from "../../../../types/key"; import { SendType } from "../../enums/send-type"; import { SendData } from "../data/send.data"; @@ -123,7 +123,12 @@ describe("Send", () => { const view = await send.decrypt(); expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey"); - expect(send.name.decrypt).toHaveBeenNthCalledWith(1, null, "cryptoKey"); + expect(send.name.decrypt).toHaveBeenNthCalledWith( + 1, + null, + "cryptoKey", + "Property: name; ObjectContext: No Domain Context", + ); expect(view).toMatchObject({ id: "id", diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 6e53813a368..c2390d439e7 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Utils } from "../../../../platform/misc/utils"; @@ -79,6 +81,8 @@ export class Send extends Domain { const sendKeyEncryptionKey = await keyService.getUserKey(); model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/tools/send/models/request/send-access.request.ts b/libs/common/src/tools/send/models/request/send-access.request.ts index 7607b03c622..bc40e220623 100644 --- a/libs/common/src/tools/send/models/request/send-access.request.ts +++ b/libs/common/src/tools/send/models/request/send-access.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class SendAccessRequest { password: string; } diff --git a/libs/common/src/tools/send/models/request/send.request.ts b/libs/common/src/tools/send/models/request/send.request.ts index e6fd2bb8fa5..9e4f1e14837 100644 --- a/libs/common/src/tools/send/models/request/send.request.ts +++ b/libs/common/src/tools/send/models/request/send.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SendType } from "../../enums/send-type"; import { SendFileApi } from "../api/send-file.api"; import { SendTextApi } from "../api/send-text.api"; diff --git a/libs/common/src/tools/send/models/response/send-access.response.ts b/libs/common/src/tools/send/models/response/send-access.response.ts index dce857ca7d4..65a98e527a4 100644 --- a/libs/common/src/tools/send/models/response/send-access.response.ts +++ b/libs/common/src/tools/send/models/response/send-access.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; import { SendType } from "../../enums/send-type"; import { SendFileApi } from "../api/send-file.api"; diff --git a/libs/common/src/tools/send/models/response/send-file-download-data.response.ts b/libs/common/src/tools/send/models/response/send-file-download-data.response.ts index 971b4975781..d43fd221adb 100644 --- a/libs/common/src/tools/send/models/response/send-file-download-data.response.ts +++ b/libs/common/src/tools/send/models/response/send-file-download-data.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; export class SendFileDownloadDataResponse extends BaseResponse { diff --git a/libs/common/src/tools/send/models/response/send-file-upload-data.response.ts b/libs/common/src/tools/send/models/response/send-file-upload-data.response.ts index 3ed5e368855..983593e3257 100644 --- a/libs/common/src/tools/send/models/response/send-file-upload-data.response.ts +++ b/libs/common/src/tools/send/models/response/send-file-upload-data.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; import { FileUploadType } from "../../../../platform/enums"; diff --git a/libs/common/src/tools/send/models/response/send.response.ts b/libs/common/src/tools/send/models/response/send.response.ts index d4dbd39f4a9..76550f5cdfd 100644 --- a/libs/common/src/tools/send/models/response/send.response.ts +++ b/libs/common/src/tools/send/models/response/send.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../../models/response/base.response"; import { SendType } from "../../enums/send-type"; import { SendFileApi } from "../api/send-file.api"; diff --git a/libs/common/src/tools/send/models/view/send-access.view.ts b/libs/common/src/tools/send/models/view/send-access.view.ts index b99a689aa91..cb8b29796af 100644 --- a/libs/common/src/tools/send/models/view/send-access.view.ts +++ b/libs/common/src/tools/send/models/view/send-access.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../../models/view/view"; import { SendType } from "../../enums/send-type"; import { SendAccess } from "../domain/send-access"; diff --git a/libs/common/src/tools/send/models/view/send-file.view.ts b/libs/common/src/tools/send/models/view/send-file.view.ts index 643487acfcd..3f1d5edd176 100644 --- a/libs/common/src/tools/send/models/view/send-file.view.ts +++ b/libs/common/src/tools/send/models/view/send-file.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../../models/view/view"; import { DeepJsonify } from "../../../../types/deep-jsonify"; import { SendFile } from "../domain/send-file"; diff --git a/libs/common/src/tools/send/models/view/send-text.view.ts b/libs/common/src/tools/send/models/view/send-text.view.ts index 57b0de4ab0f..64236e3031b 100644 --- a/libs/common/src/tools/send/models/view/send-text.view.ts +++ b/libs/common/src/tools/send/models/view/send-text.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../../models/view/view"; import { DeepJsonify } from "../../../../types/deep-jsonify"; import { SendText } from "../domain/send-text"; diff --git a/libs/common/src/tools/send/models/view/send.view.ts b/libs/common/src/tools/send/models/view/send.view.ts index 451a15d4142..2c269892a6f 100644 --- a/libs/common/src/tools/send/models/view/send.view.ts +++ b/libs/common/src/tools/send/models/view/send.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../../models/view/view"; import { Utils } from "../../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/tools/send/services/send-api.service.abstraction.ts b/libs/common/src/tools/send/services/send-api.service.abstraction.ts index 4109df19680..570f3e746a0 100644 --- a/libs/common/src/tools/send/services/send-api.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send-api.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ListResponse } from "../../../models/response/list.response"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { Send } from "../models/domain/send"; @@ -20,11 +22,6 @@ export abstract class SendApiService { postSend: (request: SendRequest) => Promise; postFileTypeSend: (request: SendRequest) => Promise; postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postSendFileLegacy: (data: FormData) => Promise; putSend: (id: string, request: SendRequest) => Promise; putSendRemovePassword: (id: string) => Promise; deleteSend: (id: string) => Promise; diff --git a/libs/common/src/tools/send/services/send-api.service.ts b/libs/common/src/tools/send/services/send-api.service.ts index ff71408bce3..f709553646f 100644 --- a/libs/common/src/tools/send/services/send-api.service.ts +++ b/libs/common/src/tools/send/services/send-api.service.ts @@ -5,7 +5,6 @@ import { FileUploadApiMethods, FileUploadService, } from "../../../platform/abstractions/file-upload/file-upload.service"; -import { Utils } from "../../../platform/misc/utils"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { SendType } from "../enums/send-type"; import { SendData } from "../models/data/send.data"; @@ -106,15 +105,6 @@ export class SendApiService implements SendApiServiceAbstraction { return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postSendFileLegacy(data: FormData): Promise { - const r = await this.apiService.send("POST", "/sends/file", data, true, true); - return new SendResponse(r); - } - async putSend(id: string, request: SendRequest): Promise { const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true); return new SendResponse(r); @@ -173,9 +163,7 @@ export class SendApiService implements SendApiServiceAbstraction { this.generateMethods(uploadDataResponse, response), ); } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { + if (e instanceof ErrorResponse) { throw new Error((e as ErrorResponse).getSingleMessage()); } else { throw e; @@ -219,35 +207,4 @@ export class SendApiService implements SendApiServiceAbstraction { return this.deleteSend(sendId); }; } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest, - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - return await this.postSendFileLegacy(fd); - } } diff --git a/libs/common/src/tools/send/services/send-state.provider.abstraction.ts b/libs/common/src/tools/send/services/send-state.provider.abstraction.ts index c16d06fb929..78c15064ff7 100644 --- a/libs/common/src/tools/send/services/send-state.provider.abstraction.ts +++ b/libs/common/src/tools/send/services/send-state.provider.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import type { Simplify } from "type-fest"; diff --git a/libs/common/src/tools/send/services/send-state.provider.ts b/libs/common/src/tools/send/services/send-state.provider.ts index 66989a70543..4adfa50eadd 100644 --- a/libs/common/src/tools/send/services/send-state.provider.ts +++ b/libs/common/src/tools/send/services/send-state.provider.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, firstValueFrom } from "rxjs"; import { ActiveUserState, CombinedState, StateProvider } from "../../../platform/state"; diff --git a/libs/common/src/tools/send/services/send.service.abstraction.ts b/libs/common/src/tools/send/services/send.service.abstraction.ts index 866a661b4a4..921f0565624 100644 --- a/libs/common/src/tools/send/services/send.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 5aca3a4b5c9..662fee02bbf 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -1,10 +1,8 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeActiveUserState, @@ -13,12 +11,14 @@ import { mockAccountServiceWith, } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EnvironmentService } from "../../../platform/abstractions/environment.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { Utils } from "../../../platform/misc/utils"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../platform/services/container.service"; +import { SelfHostedEnvironment } from "../../../platform/services/default-environment.service"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { SendType } from "../enums/send-type"; diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index 3ba1cb92e2c..7021c942d44 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -1,7 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from "rxjs"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { PBKDF2KdfConfig } from "../../../auth/models/domain/kdf-config"; +import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management"; + import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; diff --git a/libs/common/src/tools/send/services/test-data/send-tests.data.ts b/libs/common/src/tools/send/services/test-data/send-tests.data.ts index a57a39782eb..b49c3d64b95 100644 --- a/libs/common/src/tools/send/services/test-data/send-tests.data.ts +++ b/libs/common/src/tools/send/services/test-data/send-tests.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncString } from "../../../../platform/models/domain/enc-string"; import { SendType } from "../../enums/send-type"; import { SendTextApi } from "../../models/api/send-text.api"; diff --git a/libs/common/src/tools/state/buffered-state.ts b/libs/common/src/tools/state/buffered-state.ts index 3956072a443..b10ee6c7b85 100644 --- a/libs/common/src/tools/state/buffered-state.ts +++ b/libs/common/src/tools/state/buffered-state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, combineLatest, concatMap, filter, map, of, concat, merge } from "rxjs"; import { diff --git a/libs/common/src/tools/state/object-key.ts b/libs/common/src/tools/state/object-key.ts index 0593186ec43..260a2412b2c 100644 --- a/libs/common/src/tools/state/object-key.ts +++ b/libs/common/src/tools/state/object-key.ts @@ -5,6 +5,17 @@ import type { StateDefinition } from "../../platform/state/state-definition"; import { ClassifiedFormat } from "./classified-format"; import { Classifier } from "./classifier"; +/** Determines the format of persistent storage. + * `plain` storage is a plain-old javascript object. Use this type + * when you are performing your own encryption and decryption. + * `classified` uses the `ClassifiedFormat` type as its format. + * `secret-state` uses `Array` with a length of 1. + * @remarks - CAUTION! If your on-disk data is not in a correct format, + * the storage system treats the data as corrupt and returns your initial + * value. + */ +export type ObjectStorageFormat = "plain" | "classified" | "secret-state"; + /** A key for storing JavaScript objects (`{ an: "example" }`) * in a UserStateSubject. */ @@ -20,7 +31,7 @@ export type ObjectKey> key: string; state: StateDefinition; classifier: Classifier; - format: "plain" | "classified"; + format: ObjectStorageFormat; options: UserKeyDefinitionOptions; initial?: State; }; @@ -47,6 +58,18 @@ export function toUserKeyDefinition( }, ); + return classified; + } else if (key.format === "secret-state") { + const classified = new UserKeyDefinition<[ClassifiedFormat]>( + key.state, + key.key, + { + cleanupDelayMs: key.options.cleanupDelayMs, + deserializer: (jsonValue) => jsonValue as [ClassifiedFormat], + clearOn: key.options.clearOn, + }, + ); + return classified; } else { throw new Error(`unknown format: ${key.format}`); diff --git a/libs/common/src/tools/state/secret-classifier.ts b/libs/common/src/tools/state/secret-classifier.ts index 76e229f6e7e..e961ffcd20e 100644 --- a/libs/common/src/tools/state/secret-classifier.ts +++ b/libs/common/src/tools/state/secret-classifier.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Classifier } from "./classifier"; diff --git a/libs/common/src/tools/state/secret-key-definition.ts b/libs/common/src/tools/state/secret-key-definition.ts index c7ac433bdcd..8653533afb6 100644 --- a/libs/common/src/tools/state/secret-key-definition.ts +++ b/libs/common/src/tools/state/secret-key-definition.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UserKeyDefinitionOptions, UserKeyDefinition } from "../../platform/state"; // eslint-disable-next-line -- `StateDefinition` used as an argument import { StateDefinition } from "../../platform/state/state-definition"; diff --git a/libs/common/src/tools/state/secret-state.spec.ts b/libs/common/src/tools/state/secret-state.spec.ts index d4727492b34..5f679644fc7 100644 --- a/libs/common/src/tools/state/secret-state.spec.ts +++ b/libs/common/src/tools/state/secret-state.spec.ts @@ -11,11 +11,11 @@ import { import { EncString } from "../../platform/models/domain/enc-string"; import { GENERATOR_DISK } from "../../platform/state"; import { UserId } from "../../types/guid"; +import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { SecretClassifier } from "./secret-classifier"; import { SecretKeyDefinition } from "./secret-key-definition"; import { SecretState } from "./secret-state"; -import { UserEncryptor } from "./user-encryptor.abstraction"; type FooBar = { foo: boolean; bar: boolean; date?: Date }; const classifier = SecretClassifier.allSecret(); diff --git a/libs/common/src/tools/state/secret-state.ts b/libs/common/src/tools/state/secret-state.ts index 45ce855cc88..1ef2cd92b6a 100644 --- a/libs/common/src/tools/state/secret-state.ts +++ b/libs/common/src/tools/state/secret-state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable, map, concatMap, share, ReplaySubject, timer, combineLatest, of } from "rxjs"; import { EncString } from "../../platform/models/domain/enc-string"; @@ -8,10 +10,10 @@ import { CombinedState, } from "../../platform/state"; import { UserId } from "../../types/guid"; +import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { ClassifiedFormat } from "./classified-format"; import { SecretKeyDefinition } from "./secret-key-definition"; -import { UserEncryptor } from "./user-encryptor.abstraction"; const ONE_MINUTE = 1000 * 60; diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index ee78a5c048b..8111f6f9f17 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -1,16 +1,15 @@ import { BehaviorSubject, of, Subject } from "rxjs"; -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - import { awaitAsync, FakeSingleUserState, ObservableTracker } from "../../../spec"; +import { GENERATOR_DISK, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; +import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { UserBound } from "../dependencies"; import { PrivateClassifier } from "../private-classifier"; import { StateConstraints } from "../types"; import { ClassifiedFormat } from "./classified-format"; import { ObjectKey } from "./object-key"; -import { UserEncryptor } from "./user-encryptor.abstraction"; import { UserStateSubject } from "./user-state-subject"; const SomeUser = "some user" as UserId; @@ -734,6 +733,7 @@ describe("UserStateSubject", () => { error = e as any; }, }); + singleUserEncryptor$.next({ userId: SomeUser, encryptor: SomeEncryptor }); singleUserEncryptor$.next({ userId: errorUserId, encryptor: SomeEncryptor }); await awaitAsync(); diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index 0b562cc7a1f..c00560304e4 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observer, SubjectLike, @@ -6,10 +8,8 @@ import { filter, map, takeUntil, - pairwise, distinctUntilChanged, BehaviorSubject, - startWith, Observable, Subscription, last, @@ -26,19 +26,18 @@ import { skip, } from "rxjs"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SingleUserState, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - +import { EncString } from "../../platform/models/domain/enc-string"; +import { SingleUserState, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; +import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { UserBound } from "../dependencies"; -import { anyComplete, ready, withLatestReady } from "../rx"; +import { anyComplete, errorOnChange, ready, withLatestReady } from "../rx"; import { Constraints, SubjectConstraints, WithConstraints } from "../types"; import { ClassifiedFormat, isClassifiedFormat } from "./classified-format"; import { unconstrained$ } from "./identity-state-constraint"; import { isObjectKey, ObjectKey, toUserKeyDefinition } from "./object-key"; import { isDynamic } from "./state-constraints-dependency"; -import { UserEncryptor } from "./user-encryptor.abstraction"; import { UserStateSubjectDependencies } from "./user-state-subject-dependencies"; type Constrained = { constraints: Readonly>; state: State }; @@ -195,24 +194,13 @@ export class UserStateSubject< } }), // fail the stream if the state desyncs from the bound userId - startWith({ userId: this.state.userId, encryptor: null } as UserBound< - "encryptor", - UserEncryptor - >), - pairwise(), - map(([expected, actual]) => { - if (expected.userId === actual.userId) { - return actual; - } else { - throw { - expectedUserId: expected.userId, - actualUserId: actual.userId, - }; - } - }), + errorOnChange( + ({ userId }) => userId, + (expectedUserId, actualUserId) => ({ expectedUserId, actualUserId }), + ), // reduce emissions to when encryptor changes - distinctUntilChanged(), map(({ encryptor }) => encryptor), + distinctUntilChanged(), ); } @@ -317,36 +305,63 @@ export class UserStateSubject< return (input$) => input$ as Observable; } - // if the key supports encryption, enable encryptor support + // all other keys support encryption; enable encryptor support + return pipe( + this.mapToClassifiedFormat(), + combineLatestWith(encryptor$), + concatMap(async ([input, encryptor]) => { + // pass through null values + if (input === null || input === undefined) { + return null; + } + + // decrypt classified data + const { secret, disclosed } = input; + const encrypted = EncString.fromJSON(secret); + const decryptedSecret = await encryptor.decrypt(encrypted); + + // assemble into proper state + const declassified = this.objectKey.classifier.declassify(disclosed, decryptedSecret); + const state = this.objectKey.options.deserializer(declassified); + + return state; + }), + ); + } + + private mapToClassifiedFormat(): OperatorFunction> { + // FIXME: warn when data is dropped in the console and/or report an error + // through the observable; consider redirecting dropped data to a recovery + // location + + // user-state subject's default format is object-aware if (this.objectKey && this.objectKey.format === "classified") { - return pipe( - combineLatestWith(encryptor$), - concatMap(async ([input, encryptor]) => { - // pass through null values - if (input === null || input === undefined) { - return null; - } + return map((input) => { + if (!isClassifiedFormat(input)) { + return null; + } - // fail fast if the format is incorrect - if (!isClassifiedFormat(input)) { - throw new Error(`Cannot declassify ${this.key.key}; unknown format.`); - } + return input; + }); + } - // decrypt classified data - const { secret, disclosed } = input; - const encrypted = EncString.fromJSON(secret); - const decryptedSecret = await encryptor.decrypt(encrypted); + // secret state's format wraps objects in an array + if (this.objectKey && this.objectKey.format === "secret-state") { + return map((input) => { + if (!Array.isArray(input)) { + return null; + } - // assemble into proper state - const declassified = this.objectKey.classifier.declassify(disclosed, decryptedSecret); - const state = this.objectKey.options.deserializer(declassified); + const [unwrapped] = input; + if (!isClassifiedFormat(unwrapped)) { + return null; + } - return state; - }), - ); + return unwrapped; + }); } - throw new Error(`unknown serialization format: ${this.objectKey.format}`); + throw new Error(`unsupported serialization format: ${this.objectKey.format}`); } private classify(encryptor$: Observable): OperatorFunction { @@ -359,41 +374,49 @@ export class UserStateSubject< ); } - // if the key supports encryption, enable encryptor support - if (this.objectKey && this.objectKey.format === "classified") { - return pipe( - withLatestReady(encryptor$), - concatMap(async ([input, encryptor]) => { - // fail fast if there's no value - if (input === null || input === undefined) { - return null; - } + // all other keys support encryption; enable encryptor support + return pipe( + withLatestReady(encryptor$), + concatMap(async ([input, encryptor]) => { + // fail fast if there's no value + if (input === null || input === undefined) { + return null; + } - // split data by classification level - const serialized = JSON.parse(JSON.stringify(input)); - const classified = this.objectKey.classifier.classify(serialized); + // split data by classification level + const serialized = JSON.parse(JSON.stringify(input)); + const classified = this.objectKey.classifier.classify(serialized); - // protect data - const encrypted = await encryptor.encrypt(classified.secret); - const secret = JSON.parse(JSON.stringify(encrypted)); + // protect data + const encrypted = await encryptor.encrypt(classified.secret); + const secret = JSON.parse(JSON.stringify(encrypted)); - // wrap result in classified format envelope for storage - const envelope = { - id: null as void, - secret, - disclosed: classified.disclosed, - } satisfies ClassifiedFormat; + // wrap result in classified format envelope for storage + const envelope = { + id: null as void, + secret, + disclosed: classified.disclosed, + } satisfies ClassifiedFormat; - // deliberate type erasure; the type is restored during `declassify` - return envelope as unknown; - }), - ); + // deliberate type erasure; the type is restored during `declassify` + return envelope as ClassifiedFormat; + }), + this.mapToStorageFormat(), + ); + } + + private mapToStorageFormat(): OperatorFunction, unknown> { + // user-state subject's default format is object-aware + if (this.objectKey && this.objectKey.format === "classified") { + return map((input) => input as unknown); } - // FIXME: add "encrypted" format --> key contains encryption logic - // CONSIDER: should "classified format" algorithm be embedded in subject keys...? + // secret state's format wraps objects in an array + if (this.objectKey && this.objectKey.format === "secret-state") { + return map((input) => [input] as unknown); + } - throw new Error(`unknown serialization format: ${this.objectKey.format}`); + throw new Error(`unsupported serialization format: ${this.objectKey.format}`); } /** The userId to which the subject is bound. diff --git a/libs/common/src/tools/types.ts b/libs/common/src/tools/types.ts index 9b746924278..83f451351c2 100644 --- a/libs/common/src/tools/types.ts +++ b/libs/common/src/tools/types.ts @@ -28,6 +28,11 @@ type NumberConstraints = { /** maximum number value. When absent, min value is unbounded. */ max?: number; + /** recommended value. This is the value bitwarden recommends + * to the user as an appropriate value. + */ + recommendation?: number; + /** requires the number be a multiple of the step value; * this field must be a positive number. +0 and Infinity are * prohibited. When absent, any number is accepted. diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 5221f4cf0a6..88cd476606d 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -1,6 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; -import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; @@ -9,6 +10,7 @@ import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid import { UserKey } from "../../types/key"; import { CipherType } from "../enums/cipher-type"; import { CipherData } from "../models/data/cipher.data"; +import { LocalData } from "../models/data/local.data"; import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherWithIdRequest } from "../models/request/cipher-with-id.request"; @@ -24,6 +26,12 @@ export abstract class CipherService implements UserKeyRotationDataProvider; + /** + * Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed. + * + * An empty array indicates that all ciphers were successfully decrypted. + */ + failedToDecryptCiphers$: Observable; clearCache: (userId?: string) => Promise; encrypt: ( model: CipherView, diff --git a/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts index fcebb614159..868d58f5eaa 100644 --- a/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts +++ b/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts index 1762400b3dd..b95dd35e5ec 100644 --- a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts @@ -1,9 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore + +import { UserId } from "../../../types/guid"; import { Folder } from "../../models/domain/folder"; import { FolderResponse } from "../../models/response/folder.response"; export class FolderApiServiceAbstraction { - save: (folder: Folder) => Promise; - delete: (id: string) => Promise; + save: (folder: Folder, userId: UserId) => Promise; + delete: (id: string, userId: UserId) => Promise; get: (id: string) => Promise; - deleteAll: () => Promise; + deleteAll: (userId: UserId) => Promise; } diff --git a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts index df21b136f41..b7241e3ae37 100644 --- a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; @@ -11,23 +13,27 @@ import { FolderWithIdRequest } from "../../models/request/folder-with-id.request import { FolderView } from "../../models/view/folder.view"; export abstract class FolderService implements UserKeyRotationDataProvider { - folders$: Observable; - folderViews$: Observable; + folders$: (userId: UserId) => Observable; + folderViews$: (userId: UserId) => Observable; - clearCache: () => Promise; + clearDecryptedFolderState: (userId: UserId) => Promise; encrypt: (model: FolderView, key: SymmetricCryptoKey) => Promise; - get: (id: string) => Promise; - getDecrypted$: (id: string) => Observable; - getAllFromState: () => Promise; + get: (id: string, userId: UserId) => Promise; + getDecrypted$: (id: string, userId: UserId) => Observable; + /** + * @deprecated Use firstValueFrom(folders$) directly instead + * @param userId The user id + * @returns Promise of folders array + */ + getAllFromState: (userId: UserId) => Promise; /** * @deprecated Only use in CLI! */ - getFromState: (id: string) => Promise; + getFromState: (id: string, userId: UserId) => Promise; /** * @deprecated Only use in CLI! */ - getAllDecryptedFromState: () => Promise; - decryptFolders: (folders: Folder[]) => Promise; + getAllDecryptedFromState: (userId: UserId) => Promise; /** * Returns user folders re-encrypted with the new user key. * @param originalUserKey the original user key @@ -44,8 +50,8 @@ export abstract class FolderService implements UserKeyRotationDataProvider Promise; + upsert: (folder: FolderData | FolderData[], userId: UserId) => Promise; replace: (folders: { [id: string]: FolderData }, userId: UserId) => Promise; - clear: (userId?: string) => Promise; - delete: (id: string | string[]) => Promise; + clear: (userId: UserId) => Promise; + delete: (id: string | string[], userId: UserId) => Promise; } diff --git a/libs/common/src/vault/abstractions/totp.service.ts b/libs/common/src/vault/abstractions/totp.service.ts index 729588aba44..af4409a15a6 100644 --- a/libs/common/src/vault/abstractions/totp.service.ts +++ b/libs/common/src/vault/abstractions/totp.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export abstract class TotpService { getCode: (key: string) => Promise; getTimeInterval: (key: string) => number; diff --git a/libs/common/src/vault/abstractions/vault-settings/vault-settings.service.ts b/libs/common/src/vault/abstractions/vault-settings/vault-settings.service.ts index e3132d9ae1f..ea1e73c2685 100644 --- a/libs/common/src/vault/abstractions/vault-settings/vault-settings.service.ts +++ b/libs/common/src/vault/abstractions/vault-settings/vault-settings.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Observable } from "rxjs"; /** * Service for managing vault settings. @@ -17,6 +19,12 @@ export abstract class VaultSettingsService { */ showIdentitiesCurrentTab$: Observable; /** + /** + * An observable monitoring the state of the click items on the Vault view + * for Autofill suggestions. + */ + clickItemsToAutofillVaultView$: Observable; + /** /** * Saves the enable passkeys setting to disk. @@ -33,4 +41,10 @@ export abstract class VaultSettingsService { * @param value The new value for the show identities on tab page setting. */ setShowIdentitiesCurrentTab: (value: boolean) => Promise; + /** + * Saves the click items on vault View for Autofill suggestions to disk. + * @param value The new value for the click items on vault View for + * Autofill suggestions setting. + */ + setClickItemsToAutofillVaultView: (value: boolean) => Promise; } diff --git a/libs/common/src/vault/abstractions/view-password-history.service.ts b/libs/common/src/vault/abstractions/view-password-history.service.ts index d9b1306eacb..2e47a456ff2 100644 --- a/libs/common/src/vault/abstractions/view-password-history.service.ts +++ b/libs/common/src/vault/abstractions/view-password-history.service.ts @@ -1,8 +1,8 @@ -import { CipherId } from "../../types/guid"; +import { CipherView } from "../models/view/cipher.view"; /** * The ViewPasswordHistoryService is responsible for displaying the password history for a cipher. */ export abstract class ViewPasswordHistoryService { - abstract viewPasswordHistory(cipherId?: CipherId): Promise; + abstract viewPasswordHistory(cipher: CipherView): Promise; } diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts index 78e6ecd7b4f..5775bc7f55e 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.ts @@ -43,10 +43,18 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1; } + if (isWebsite && (hostnameUri.endsWith(".onion") || hostnameUri.endsWith(".i2p"))) { + image = null; + fallbackImage = "images/bwi-globe.png"; + break; + } + if (showFavicon && isWebsite) { try { image = `${iconsServerUrl}/${Utils.getHostname(hostnameUri)}/icon.png`; fallbackImage = "images/bwi-globe.png"; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error since the fallback icon will be shown if image is null. } diff --git a/libs/common/src/vault/linked-field-option.decorator.ts b/libs/common/src/vault/linked-field-option.decorator.ts index 91fe1ee28f1..96a47753e8a 100644 --- a/libs/common/src/vault/linked-field-option.decorator.ts +++ b/libs/common/src/vault/linked-field-option.decorator.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LinkedIdType } from "./enums"; import { ItemView } from "./models/view/item.view"; diff --git a/libs/common/src/vault/models/api/card.api.ts b/libs/common/src/vault/models/api/card.api.ts index da68d000ddc..3b5b7722a00 100644 --- a/libs/common/src/vault/models/api/card.api.ts +++ b/libs/common/src/vault/models/api/card.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class CardApi extends BaseResponse { diff --git a/libs/common/src/vault/models/api/fido2-credential.api.ts b/libs/common/src/vault/models/api/fido2-credential.api.ts index 0ceaa7fce89..d07e1666fb8 100644 --- a/libs/common/src/vault/models/api/fido2-credential.api.ts +++ b/libs/common/src/vault/models/api/fido2-credential.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class Fido2CredentialApi extends BaseResponse { diff --git a/libs/common/src/vault/models/api/field.api.ts b/libs/common/src/vault/models/api/field.api.ts index b319d138009..fffb962da95 100644 --- a/libs/common/src/vault/models/api/field.api.ts +++ b/libs/common/src/vault/models/api/field.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { FieldType, LinkedIdType } from "../../enums"; diff --git a/libs/common/src/vault/models/api/identity.api.ts b/libs/common/src/vault/models/api/identity.api.ts index 795bf83b2bb..803989390ec 100644 --- a/libs/common/src/vault/models/api/identity.api.ts +++ b/libs/common/src/vault/models/api/identity.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class IdentityApi extends BaseResponse { diff --git a/libs/common/src/vault/models/api/login-uri.api.ts b/libs/common/src/vault/models/api/login-uri.api.ts index 853f181654b..83198668ecf 100644 --- a/libs/common/src/vault/models/api/login-uri.api.ts +++ b/libs/common/src/vault/models/api/login-uri.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/vault/models/api/login.api.ts b/libs/common/src/vault/models/api/login.api.ts index 63cdd3db981..62582ae221a 100644 --- a/libs/common/src/vault/models/api/login.api.ts +++ b/libs/common/src/vault/models/api/login.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { JsonObject } from "type-fest"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/vault/models/api/secure-note.api.ts b/libs/common/src/vault/models/api/secure-note.api.ts index 6d7826b5fa7..bc1d112cd4d 100644 --- a/libs/common/src/vault/models/api/secure-note.api.ts +++ b/libs/common/src/vault/models/api/secure-note.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { SecureNoteType } from "../../enums"; diff --git a/libs/common/src/vault/models/api/ssh-key.api.ts b/libs/common/src/vault/models/api/ssh-key.api.ts index e14f72bbc6a..8d47e1fa5a7 100644 --- a/libs/common/src/vault/models/api/ssh-key.api.ts +++ b/libs/common/src/vault/models/api/ssh-key.api.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; export class SshKeyApi extends BaseResponse { diff --git a/libs/common/src/vault/models/data/attachment.data.ts b/libs/common/src/vault/models/data/attachment.data.ts index a0bcad84dfe..dfc9f9d1afa 100644 --- a/libs/common/src/vault/models/data/attachment.data.ts +++ b/libs/common/src/vault/models/data/attachment.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AttachmentResponse } from "../response/attachment.response"; export class AttachmentData { diff --git a/libs/common/src/vault/models/data/card.data.ts b/libs/common/src/vault/models/data/card.data.ts index fe80003a8f5..677c33f4886 100644 --- a/libs/common/src/vault/models/data/card.data.ts +++ b/libs/common/src/vault/models/data/card.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CardApi } from "../api/card.api"; export class CardData { diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts index 476c651f3ae..1c86f91c82f 100644 --- a/libs/common/src/vault/models/data/cipher.data.ts +++ b/libs/common/src/vault/models/data/cipher.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; diff --git a/libs/common/src/vault/models/data/fido2-credential.data.ts b/libs/common/src/vault/models/data/fido2-credential.data.ts index 230def35f21..94716e8d86c 100644 --- a/libs/common/src/vault/models/data/fido2-credential.data.ts +++ b/libs/common/src/vault/models/data/fido2-credential.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Fido2CredentialApi } from "../api/fido2-credential.api"; export class Fido2CredentialData { diff --git a/libs/common/src/vault/models/data/field.data.ts b/libs/common/src/vault/models/data/field.data.ts index 0733b6c0f05..b9daf7fa423 100644 --- a/libs/common/src/vault/models/data/field.data.ts +++ b/libs/common/src/vault/models/data/field.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FieldType, LinkedIdType } from "../../enums"; import { FieldApi } from "../api/field.api"; diff --git a/libs/common/src/vault/models/data/folder.data.ts b/libs/common/src/vault/models/data/folder.data.ts index f59e81025b5..c2eb585a6f4 100644 --- a/libs/common/src/vault/models/data/folder.data.ts +++ b/libs/common/src/vault/models/data/folder.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { FolderResponse } from "../response/folder.response"; diff --git a/libs/common/src/vault/models/data/identity.data.ts b/libs/common/src/vault/models/data/identity.data.ts index 620ec2ec433..158daace371 100644 --- a/libs/common/src/vault/models/data/identity.data.ts +++ b/libs/common/src/vault/models/data/identity.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { IdentityApi } from "../api/identity.api"; export class IdentityData { diff --git a/libs/common/src/vault/models/data/login-uri.data.ts b/libs/common/src/vault/models/data/login-uri.data.ts index 99d2f9a9eb9..852dad4e112 100644 --- a/libs/common/src/vault/models/data/login-uri.data.ts +++ b/libs/common/src/vault/models/data/login-uri.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { LoginUriApi } from "../api/login-uri.api"; diff --git a/libs/common/src/vault/models/data/login.data.ts b/libs/common/src/vault/models/data/login.data.ts index 766718fa884..0fe021d923c 100644 --- a/libs/common/src/vault/models/data/login.data.ts +++ b/libs/common/src/vault/models/data/login.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LoginApi } from "../api/login.api"; import { Fido2CredentialData } from "./fido2-credential.data"; diff --git a/libs/common/src/vault/models/data/password-history.data.ts b/libs/common/src/vault/models/data/password-history.data.ts index 5b8ea076b22..75a51ed3728 100644 --- a/libs/common/src/vault/models/data/password-history.data.ts +++ b/libs/common/src/vault/models/data/password-history.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { PasswordHistoryResponse } from "../response/password-history.response"; export class PasswordHistoryData { diff --git a/libs/common/src/vault/models/data/secure-note.data.ts b/libs/common/src/vault/models/data/secure-note.data.ts index f156eec2974..7d109398ab7 100644 --- a/libs/common/src/vault/models/data/secure-note.data.ts +++ b/libs/common/src/vault/models/data/secure-note.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SecureNoteType } from "../../enums"; import { SecureNoteApi } from "../api/secure-note.api"; diff --git a/libs/common/src/vault/models/data/ssh-key.data.ts b/libs/common/src/vault/models/data/ssh-key.data.ts index 32b6ec994f3..2b93c93d931 100644 --- a/libs/common/src/vault/models/data/ssh-key.data.ts +++ b/libs/common/src/vault/models/data/ssh-key.data.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { SshKeyApi } from "../api/ssh-key.api"; export class SshKeyData { diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 14dec8dea0c..8cae7170738 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; @@ -101,7 +103,7 @@ describe("Attachment", () => { it("uses the provided key without depending on KeyService", async () => { const providedKey = mock(); - await attachment.decrypt(null, providedKey); + await attachment.decrypt(null, "", providedKey); expect(keyService.getUserKeyWithLegacySupport).not.toHaveBeenCalled(); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey); @@ -111,7 +113,7 @@ describe("Attachment", () => { const orgKey = mock(); keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); - await attachment.decrypt("orgId", null); + await attachment.decrypt("orgId", "", null); expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey); @@ -121,7 +123,7 @@ describe("Attachment", () => { const userKey = mock(); keyService.getUserKeyWithLegacySupport.mockResolvedValue(userKey); - await attachment.decrypt(null, null); + await attachment.decrypt(null, "", null); expect(keyService.getUserKeyWithLegacySupport).toHaveBeenCalled(); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 117b3b26e92..4eee0307746 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Utils } from "../../../platform/misc/utils"; @@ -36,7 +38,11 @@ export class Attachment extends Domain { ); } - async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { const view = await this.decryptObj( new AttachmentView(this), { @@ -44,6 +50,7 @@ export class Attachment extends Domain { }, orgId, encKey, + "DomainType: Attachment; " + context, ); if (this.key != null) { @@ -62,6 +69,8 @@ export class Attachment extends Domain { const encryptService = Utils.getContainerService().getEncryptService(); const decValue = await encryptService.decryptToBytes(this.key, encKey); return new SymmetricCryptoKey(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index d204e104c93..fccfe3f595b 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; @@ -35,7 +37,11 @@ export class Card extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new CardView(), { @@ -48,6 +54,7 @@ export class Card extends Domain { }, orgId, encKey, + "DomainType: Card; " + context, ); } diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 509a17a8a0e..dd79da3086e 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1,8 +1,8 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; @@ -10,6 +10,7 @@ import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { EncString } from "../../../platform/models/domain/enc-string"; import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; +import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; import { FieldType, SecureNoteType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 79536f5379a..d82f4585e65 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; @@ -134,7 +136,17 @@ export class Cipher extends Domain implements Decryptable { if (this.key != null) { const encryptService = Utils.getContainerService().getEncryptService(); - encKey = new SymmetricCryptoKey(await encryptService.decryptToBytes(this.key, encKey)); + const keyBytes = await encryptService.decryptToBytes( + this.key, + encKey, + `Cipher Id: ${this.id}; Content: CipherKey; IsEncryptedByOrgKey: ${this.organizationId != null}`, + ); + if (keyBytes == null) { + model.name = "[error: cannot decrypt]"; + model.decryptionFailure = true; + return model; + } + encKey = new SymmetricCryptoKey(keyBytes); bypassValidation = false; } @@ -150,19 +162,36 @@ export class Cipher extends Domain implements Decryptable { switch (this.type) { case CipherType.Login: - model.login = await this.login.decrypt(this.organizationId, bypassValidation, encKey); + model.login = await this.login.decrypt( + this.organizationId, + bypassValidation, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.SecureNote: - model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey); + model.secureNote = await this.secureNote.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.Card: - model.card = await this.card.decrypt(this.organizationId, encKey); + model.card = await this.card.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); break; case CipherType.Identity: - model.identity = await this.identity.decrypt(this.organizationId, encKey); + model.identity = await this.identity.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.SshKey: - model.sshKey = await this.sshKey.decrypt(this.organizationId, encKey); + model.sshKey = await this.sshKey.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; default: break; @@ -173,7 +202,7 @@ export class Cipher extends Domain implements Decryptable { await this.attachments.reduce((promise, attachment) => { return promise .then(() => { - return attachment.decrypt(this.organizationId, encKey); + return attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); }) .then((decAttachment) => { attachments.push(decAttachment); diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index 768e3983e89..9aa2c753d7c 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index a30b4f95a54..f836184da6a 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; diff --git a/libs/common/src/vault/models/domain/folder.ts b/libs/common/src/vault/models/domain/folder.ts index da9e9811d4c..93d04607af5 100644 --- a/libs/common/src/vault/models/domain/folder.ts +++ b/libs/common/src/vault/models/domain/folder.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index 6066e5de22c..570e6c0b4d5 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; @@ -59,7 +61,11 @@ export class Identity extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context: string = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new IdentityView(), { @@ -84,6 +90,7 @@ export class Identity extends Domain { }, orgId, encKey, + "DomainType: Identity; " + context, ); } diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index e5943929f2d..36782a81502 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; @@ -31,7 +33,11 @@ export class LoginUri extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context: string = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new LoginUriView(this), { @@ -39,6 +45,7 @@ export class LoginUri extends Domain { }, orgId, encKey, + context, ); } diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index dfd95f83d70..f9a85cd818e 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; @@ -53,6 +55,7 @@ export class Login extends Domain { async decrypt( orgId: string, bypassValidation: boolean, + context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { const view = await this.decryptObj( @@ -64,6 +67,7 @@ export class Login extends Domain { }, orgId, encKey, + `DomainType: Login; ${context}`, ); if (this.uris != null) { @@ -74,7 +78,7 @@ export class Login extends Domain { continue; } - const uri = await this.uris[i].decrypt(orgId, encKey); + const uri = await this.uris[i].decrypt(orgId, context, encKey); // URIs are shared remotely after decryption // we need to validate that the string hasn't been changed by a compromised server // This validation is tied to the existence of cypher.key for backwards compatibility diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index b6eec21f1a6..48063f495f0 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; @@ -30,6 +32,7 @@ export class Password extends Domain { }, orgId, encKey, + "DomainType: PasswordHistory", ); } diff --git a/libs/common/src/vault/models/domain/secure-note.ts b/libs/common/src/vault/models/domain/secure-note.ts index 7ae16f099a8..693ae38d9fb 100644 --- a/libs/common/src/vault/models/domain/secure-note.ts +++ b/libs/common/src/vault/models/domain/secure-note.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import Domain from "../../../platform/models/domain/domain-base"; @@ -18,8 +20,12 @@ export class SecureNote extends Domain { this.type = obj.type; } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { - return Promise.resolve(new SecureNoteView(this)); + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { + return new SecureNoteView(this); } toSecureNoteData(): SecureNoteData { diff --git a/libs/common/src/vault/models/domain/sorted-ciphers-cache.ts b/libs/common/src/vault/models/domain/sorted-ciphers-cache.ts index 330a7905e5c..49c6eed4741 100644 --- a/libs/common/src/vault/models/domain/sorted-ciphers-cache.ts +++ b/libs/common/src/vault/models/domain/sorted-ciphers-cache.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CipherView } from "../view/cipher.view"; const CacheTTL = 3000; diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index e7c24b45ba8..b4df172e543 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -1,8 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; - import Domain from "../../../platform/models/domain/domain-base"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SshKeyData } from "../data/ssh-key.data"; import { SshKeyView } from "../view/ssh-key.view"; @@ -30,7 +31,11 @@ export class SshKey extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new SshKeyView(), { @@ -40,6 +45,7 @@ export class SshKey extends Domain { }, orgId, encKey, + "DomainType: SshKey; " + context, ); } diff --git a/libs/common/src/vault/models/request/attachment.request.ts b/libs/common/src/vault/models/request/attachment.request.ts index ea1ea82112c..d058fa69d8b 100644 --- a/libs/common/src/vault/models/request/attachment.request.ts +++ b/libs/common/src/vault/models/request/attachment.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class AttachmentRequest { fileName: string; key: string; diff --git a/libs/common/src/vault/models/request/cipher-bulk-delete.request.ts b/libs/common/src/vault/models/request/cipher-bulk-delete.request.ts index 227f1a66991..5694ca26b55 100644 --- a/libs/common/src/vault/models/request/cipher-bulk-delete.request.ts +++ b/libs/common/src/vault/models/request/cipher-bulk-delete.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class CipherBulkDeleteRequest { ids: string[]; organizationId: string; diff --git a/libs/common/src/vault/models/request/cipher-bulk-restore.request.ts b/libs/common/src/vault/models/request/cipher-bulk-restore.request.ts index 89dc0cb5c15..23432ec6ed6 100644 --- a/libs/common/src/vault/models/request/cipher-bulk-restore.request.ts +++ b/libs/common/src/vault/models/request/cipher-bulk-restore.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class CipherBulkRestoreRequest { ids: string[]; organizationId: string; diff --git a/libs/common/src/vault/models/request/cipher-bulk-share.request.ts b/libs/common/src/vault/models/request/cipher-bulk-share.request.ts index 33a7370b4de..4f56297d0a5 100644 --- a/libs/common/src/vault/models/request/cipher-bulk-share.request.ts +++ b/libs/common/src/vault/models/request/cipher-bulk-share.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Cipher } from "../domain/cipher"; import { CipherWithIdRequest } from "./cipher-with-id.request"; diff --git a/libs/common/src/vault/models/request/cipher.request.ts b/libs/common/src/vault/models/request/cipher.request.ts index f24254f7432..5b77ee7508e 100644 --- a/libs/common/src/vault/models/request/cipher.request.ts +++ b/libs/common/src/vault/models/request/cipher.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; import { CardApi } from "../api/card.api"; diff --git a/libs/common/src/vault/models/request/folder.request.ts b/libs/common/src/vault/models/request/folder.request.ts index a37f66ddfab..c3eb8ea1674 100644 --- a/libs/common/src/vault/models/request/folder.request.ts +++ b/libs/common/src/vault/models/request/folder.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Folder } from "../domain/folder"; export class FolderRequest { diff --git a/libs/common/src/vault/models/request/password-history.request.ts b/libs/common/src/vault/models/request/password-history.request.ts index 6cca2b86d2d..53fc2225746 100644 --- a/libs/common/src/vault/models/request/password-history.request.ts +++ b/libs/common/src/vault/models/request/password-history.request.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export class PasswordHistoryRequest { password: string; lastUsedDate: Date; diff --git a/libs/common/src/vault/models/response/attachment-upload-data.response.ts b/libs/common/src/vault/models/response/attachment-upload-data.response.ts index cfbd38beaaa..0bd31154ff7 100644 --- a/libs/common/src/vault/models/response/attachment-upload-data.response.ts +++ b/libs/common/src/vault/models/response/attachment-upload-data.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { FileUploadType } from "../../../platform/enums"; diff --git a/libs/common/src/vault/models/response/cipher.response.ts b/libs/common/src/vault/models/response/cipher.response.ts index 7e2805b7510..ee0d36aed97 100644 --- a/libs/common/src/vault/models/response/cipher.response.ts +++ b/libs/common/src/vault/models/response/cipher.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CardApi } from "../api/card.api"; diff --git a/libs/common/src/vault/models/view/attachment.view.ts b/libs/common/src/vault/models/view/attachment.view.ts index 0c6bd980b07..09839ed2fef 100644 --- a/libs/common/src/vault/models/view/attachment.view.ts +++ b/libs/common/src/vault/models/view/attachment.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { View } from "../../../models/view/view"; diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index fad10851e6a..9eeb4dabf4d 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { normalizeExpiryYearFormat } from "../../../autofill/utils"; diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index 4d429bb390f..20dbd23065c 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../models/view/view"; import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; @@ -44,6 +46,11 @@ export class CipherView implements View, InitializerMetadata { deletedDate: Date = null; reprompt: CipherRepromptType = CipherRepromptType.None; + /** + * Flag to indicate if the cipher decryption failed. + */ + decryptionFailure = false; + constructor(c?: Cipher) { if (!c) { return; @@ -148,6 +155,8 @@ export class CipherView implements View, InitializerMetadata { return null; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars const item = this.item; return this.item[linkedFieldOption.propertyKey as keyof typeof item]; } diff --git a/libs/common/src/vault/models/view/fido2-credential-autofill.view.ts b/libs/common/src/vault/models/view/fido2-credential-autofill.view.ts new file mode 100644 index 00000000000..0735b97718c --- /dev/null +++ b/libs/common/src/vault/models/view/fido2-credential-autofill.view.ts @@ -0,0 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +export class Fido2CredentialAutofillView { + cipherId: string; + credentialId: string; + rpId: string; + userHandle: string; + userName: string; +} diff --git a/libs/common/src/vault/models/view/fido2-credential.view.ts b/libs/common/src/vault/models/view/fido2-credential.view.ts index f07347f6626..b364d63b8ea 100644 --- a/libs/common/src/vault/models/view/fido2-credential.view.ts +++ b/libs/common/src/vault/models/view/fido2-credential.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { ItemView } from "./item.view"; diff --git a/libs/common/src/vault/models/view/field.view.ts b/libs/common/src/vault/models/view/field.view.ts index 1428b20b7fb..ef8c5113fd0 100644 --- a/libs/common/src/vault/models/view/field.view.ts +++ b/libs/common/src/vault/models/view/field.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { View } from "../../../models/view/view"; diff --git a/libs/common/src/vault/models/view/folder.view.ts b/libs/common/src/vault/models/view/folder.view.ts index 47659c2739c..bc908e98eb8 100644 --- a/libs/common/src/vault/models/view/folder.view.ts +++ b/libs/common/src/vault/models/view/folder.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { View } from "../../../models/view/view"; diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index 8854b8664e6..247e5cfec86 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { Utils } from "../../../platform/misc/utils"; diff --git a/libs/common/src/vault/models/view/item.view.ts b/libs/common/src/vault/models/view/item.view.ts index 70e4afccc78..3954276ca04 100644 --- a/libs/common/src/vault/models/view/item.view.ts +++ b/libs/common/src/vault/models/view/item.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { View } from "../../../models/view/view"; import { LinkedMetadata } from "../../linked-field-option.decorator"; diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index f3bc0a492da..315adb87c75 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; @@ -140,6 +142,8 @@ export class LoginUriView implements View { try { const regex = new RegExp(this.uri, "i"); return regex.test(targetUri); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Invalid regex return false; diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 2f525b24136..228f3a60c34 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; import { DeepJsonify } from "../../../types/deep-jsonify"; diff --git a/libs/common/src/vault/models/view/password-history.view.ts b/libs/common/src/vault/models/view/password-history.view.ts index bf456254846..3ab360d5e09 100644 --- a/libs/common/src/vault/models/view/password-history.view.ts +++ b/libs/common/src/vault/models/view/password-history.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { View } from "../../../models/view/view"; diff --git a/libs/common/src/vault/models/view/secure-note.view.ts b/libs/common/src/vault/models/view/secure-note.view.ts index 6267280b463..c7dd4f8932d 100644 --- a/libs/common/src/vault/models/view/secure-note.view.ts +++ b/libs/common/src/vault/models/view/secure-note.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { SecureNoteType } from "../../enums"; diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts index 4fedb1f8a36..8f1a9c5a65a 100644 --- a/libs/common/src/vault/models/view/ssh-key.view.ts +++ b/libs/common/src/vault/models/view/ssh-key.view.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { SshKey } from "../domain/ssh-key"; @@ -17,6 +19,10 @@ export class SshKeyView extends ItemView { } get maskedPrivateKey(): string { + if (!this.privateKey || this.privateKey.length === 0) { + return ""; + } + let lines = this.privateKey.split("\n").filter((l) => l.trim() !== ""); lines = lines.map((l, i) => { if (i === 0 || i === lines.length - 1) { diff --git a/libs/common/src/vault/service-utils.ts b/libs/common/src/vault/service-utils.ts index c6337c49bbe..5fbc550d6af 100644 --- a/libs/common/src/vault/service-utils.ts +++ b/libs/common/src/vault/service-utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ITreeNodeObject, TreeNode } from "./models/domain/tree-node"; export class ServiceUtils { diff --git a/libs/common/src/vault/services/cipher-authorization.service.spec.ts b/libs/common/src/vault/services/cipher-authorization.service.spec.ts index cccd29ad697..15e8b03bfad 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.spec.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.spec.ts @@ -2,10 +2,10 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "../../admin-console/models/domain/organization"; +import { CollectionId } from "../../types/guid"; import { CipherView } from "../models/view/cipher.view"; import { diff --git a/libs/common/src/vault/services/cipher-authorization.service.ts b/libs/common/src/vault/services/cipher-authorization.service.ts index eb6211848ae..025d6b1cdc3 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.ts @@ -1,9 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { map, Observable, of, shareReplay, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { CollectionId } from "../../types/guid"; import { Cipher } from "../models/domain/cipher"; import { CipherView } from "../models/view/cipher.view"; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 961bc03bbb0..0d6578f165d 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -1,8 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, map, of } from "rxjs"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; - +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CipherDecryptionKeys, KeyService, @@ -15,6 +15,7 @@ import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { UriMatchStrategy } from "../../models/domain/domain-service"; +import { BulkEncryptService } from "../../platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; @@ -229,11 +230,11 @@ describe("Cipher Service", () => { }); describe("updateWithServer()", () => { - it("should call apiService.putCipherAdmin when orgAdmin and isNotClone params are true", async () => { + it("should call apiService.putCipherAdmin when orgAdmin param is true", async () => { const spy = jest .spyOn(apiService, "putCipherAdmin") .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); - await cipherService.updateWithServer(cipherObj, true, true); + await cipherService.updateWithServer(cipherObj, true); const expectedObj = new CipherRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -252,7 +253,7 @@ describe("Cipher Service", () => { expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj); }); - it("should call apiService.putPartialCipher when orgAdmin, isNotClone, and edit are false", async () => { + it("should call apiService.putPartialCipher when orgAdmin, and edit are false", async () => { cipherObj.edit = false; const spy = jest .spyOn(apiService, "putPartialCipher") @@ -359,6 +360,7 @@ describe("Cipher Service", () => { const originalUserKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; const newUserKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; let decryptedCiphers: BehaviorSubject>; + let failedCiphers: BehaviorSubject; let encryptedKey: EncString; beforeEach(() => { @@ -385,6 +387,7 @@ describe("Cipher Service", () => { Cipher2: cipher2, }); cipherService.cipherViews$ = decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))); + cipherService.failedToDecryptCiphers$ = failedCiphers = new BehaviorSubject([]); encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptedKey = new EncString("Re-encrypted Cipher Key"); @@ -413,5 +416,16 @@ describe("Cipher Service", () => { "New user key is required to rotate ciphers", ); }); + + it("throws if the user has any failed to decrypt ciphers", async () => { + const badCipher = new CipherView(cipherObj); + badCipher.id = "Cipher 3"; + badCipher.organizationId = null; + badCipher.decryptionFailure = true; + failedCiphers.next([badCipher]); + await expect( + cipherService.getRotatedData(originalUserKey, newUserKey, mockUserId), + ).rejects.toThrow("Cannot rotate ciphers when decryption failures are present"); + }); }); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 474976932e8..fe946fbb064 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { combineLatest, filter, @@ -5,25 +7,27 @@ import { map, merge, Observable, + of, shareReplay, Subject, switchMap, } from "rxjs"; import { SemVer } from "semver"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; - +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; +import { AccountService } from "../../auth/abstractions/account.service"; import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; import { View } from "../../models/view/view"; +import { BulkEncryptService } from "../../platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; @@ -77,6 +81,7 @@ import { ADD_EDIT_CIPHER_INFO_KEY, DECRYPTED_CIPHERS, ENCRYPTED_CIPHERS, + FAILED_DECRYPTED_CIPHERS, LOCAL_DATA_KEY, } from "./key-state/ciphers.state"; @@ -107,9 +112,17 @@ export class CipherService implements CipherServiceAbstraction { cipherViews$: Observable; addEditCipherInfo$: Observable; + /** + * Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed. + * + * An empty array indicates that all ciphers were successfully decrypted. + */ + failedToDecryptCiphers$: Observable; + private localDataState: ActiveUserState>; private encryptedCiphersState: ActiveUserState>; private decryptedCiphersState: ActiveUserState>; + private failedToDecryptCiphersState: ActiveUserState; private addEditCipherInfoState: ActiveUserState; constructor( @@ -130,6 +143,7 @@ export class CipherService implements CipherServiceAbstraction { this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY); this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS); this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS); + this.failedToDecryptCiphersState = this.stateProvider.getActive(FAILED_DECRYPTED_CIPHERS); this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {})); @@ -141,6 +155,13 @@ export class CipherService implements CipherServiceAbstraction { switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted())), shareReplay({ bufferSize: 1, refCount: true }), ); + + this.failedToDecryptCiphers$ = this.failedToDecryptCiphersState.state$.pipe( + filter((ciphers) => ciphers != null), + switchMap((ciphers) => merge(this.forceCipherViews$, of(ciphers))), + shareReplay({ bufferSize: 1, refCount: true }), + ); + this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; } @@ -160,6 +181,10 @@ export class CipherService implements CipherServiceAbstraction { } } + async setFailedDecryptedCiphers(cipherViews: CipherView[], userId: UserId) { + await this.stateProvider.setUserState(FAILED_DECRYPTED_CIPHERS, cipherViews, userId); + } + private async setDecryptedCiphers(value: CipherView[], userId: UserId) { const cipherViews: { [id: string]: CipherView } = {}; value?.forEach((c) => { @@ -376,7 +401,7 @@ export class CipherService implements CipherServiceAbstraction { */ @sequentialize(() => "getAllDecrypted") async getAllDecrypted(): Promise { - let decCiphers = await this.getDecryptedCiphers(); + const decCiphers = await this.getDecryptedCiphers(); if (decCiphers != null && decCiphers.length !== 0) { await this.reindexCiphers(); return await this.getDecryptedCiphers(); @@ -388,10 +413,15 @@ export class CipherService implements CipherServiceAbstraction { return []; } - decCiphers = await this.decryptCiphers(await this.getAll(), activeUserId); + const [newDecCiphers, failedCiphers] = await this.decryptCiphers( + await this.getAll(), + activeUserId, + ); - await this.setDecryptedCipherCache(decCiphers, activeUserId); - return decCiphers; + await this.setDecryptedCipherCache(newDecCiphers, activeUserId); + await this.setFailedDecryptedCiphers(failedCiphers, activeUserId); + + return newDecCiphers; } private async getDecryptedCiphers() { @@ -400,7 +430,17 @@ export class CipherService implements CipherServiceAbstraction { ); } - private async decryptCiphers(ciphers: Cipher[], userId: UserId) { + /** + * Decrypts the provided ciphers using the provided user's keys. + * @param ciphers + * @param userId + * @returns Two cipher arrays, the first containing successfully decrypted ciphers and the second containing ciphers that failed to decrypt. + * @private + */ + private async decryptCiphers( + ciphers: Cipher[], + userId: UserId, + ): Promise<[CipherView[], CipherView[]]> { const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true)); if (keys == null || (keys.userKey == null && Object.keys(keys.orgKeys).length === 0)) { @@ -418,7 +458,7 @@ export class CipherService implements CipherServiceAbstraction { {} as Record, ); - const decCiphers = ( + const allCipherViews = ( await Promise.all( Object.entries(grouped).map(async ([orgId, groupedCiphers]) => { if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) { @@ -438,7 +478,18 @@ export class CipherService implements CipherServiceAbstraction { .flat() .sort(this.getLocaleSortingFunction()); - return decCiphers; + // Split ciphers into two arrays, one for successfully decrypted ciphers and one for ciphers that failed to decrypt + return allCipherViews.reduce( + (acc, c) => { + if (c.decryptionFailure) { + acc[1].push(c); + } else { + acc[0].push(c); + } + return acc; + }, + [[], []] as [CipherView[], CipherView[]], + ); } private async reindexCiphers() { @@ -709,13 +760,9 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(updated[cipher.id as CipherId]); } - async updateWithServer( - cipher: Cipher, - orgAdmin?: boolean, - isNotClone?: boolean, - ): Promise { + async updateWithServer(cipher: Cipher, orgAdmin?: boolean): Promise { let response: CipherResponse; - if (orgAdmin && isNotClone) { + if (orgAdmin) { const request = new CipherRequest(cipher); response = await this.apiService.putCipherAdmin(cipher.id, request); const data = new CipherData(response, cipher.collectionIds); @@ -1274,10 +1321,15 @@ export class CipherService implements CipherServiceAbstraction { let encryptedCiphers: CipherWithIdRequest[] = []; const ciphers = await firstValueFrom(this.cipherViews$); + const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$); if (!ciphers) { return encryptedCiphers; } + if (failedCiphers.length > 0) { + throw new Error("Cannot rotate ciphers when decryption failures are present"); + } + const userCiphers = ciphers.filter((c) => c.organizationId == null); if (userCiphers.length === 0) { return encryptedCiphers; @@ -1638,6 +1690,7 @@ export class CipherService implements CipherServiceAbstraction { private async clearDecryptedCiphersState(userId: UserId) { await this.setDecryptedCiphers(null, userId); + await this.setFailedDecryptedCiphers(null, userId); this.clearSortedCiphers(); } diff --git a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts index 60561be1a75..4dd2f7f7338 100644 --- a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts +++ b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ApiService } from "../../../abstractions/api.service"; import { ErrorResponse } from "../../../models/response/error.response"; import { diff --git a/libs/common/src/vault/services/folder/folder-api.service.ts b/libs/common/src/vault/services/folder/folder-api.service.ts index e46df37c176..9d434cc646d 100644 --- a/libs/common/src/vault/services/folder/folder-api.service.ts +++ b/libs/common/src/vault/services/folder/folder-api.service.ts @@ -1,4 +1,5 @@ import { ApiService } from "../../../abstractions/api.service"; +import { UserId } from "../../../types/guid"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; import { FolderData } from "../../../vault/models/data/folder.data"; @@ -12,7 +13,7 @@ export class FolderApiService implements FolderApiServiceAbstraction { private apiService: ApiService, ) {} - async save(folder: Folder): Promise { + async save(folder: Folder, userId: UserId): Promise { const request = new FolderRequest(folder); let response: FolderResponse; @@ -24,17 +25,17 @@ export class FolderApiService implements FolderApiServiceAbstraction { } const data = new FolderData(response); - await this.folderService.upsert(data); + await this.folderService.upsert(data, userId); } - async delete(id: string): Promise { + async delete(id: string, userId: UserId): Promise { await this.deleteFolder(id); - await this.folderService.delete(id); + await this.folderService.delete(id, userId); } - async deleteAll(): Promise { + async deleteAll(userId: UserId): Promise { await this.apiService.send("DELETE", "/folders/all", null, true, false); - await this.folderService.clear(); + await this.folderService.clear(userId); } async get(id: string): Promise { diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 193d0e85e61..9fdb4327b98 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,10 +1,12 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { makeStaticByteArray } from "../../../../spec"; +import { makeEncString } from "../../../../spec"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; -import { FakeActiveUserState } from "../../../../spec/fake-state"; +import { FakeSingleUserState } from "../../../../spec/fake-state"; import { FakeStateProvider } from "../../../../spec/fake-state-provider"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; @@ -17,7 +19,7 @@ import { CipherService } from "../../abstractions/cipher.service"; import { FolderData } from "../../models/data/folder.data"; import { FolderView } from "../../models/view/folder.view"; import { FolderService } from "../../services/folder/folder.service"; -import { FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; +import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; describe("Folder Service", () => { let folderService: FolderService; @@ -30,7 +32,7 @@ describe("Folder Service", () => { const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; - let folderState: FakeActiveUserState>; + let folderState: FakeSingleUserState>; beforeEach(() => { keyService = mock(); @@ -42,11 +44,9 @@ describe("Folder Service", () => { stateProvider = new FakeStateProvider(accountService); i18nService.collator = new Intl.Collator("en"); + i18nService.t.mockReturnValue("No Folder"); - keyService.hasUserKey.mockResolvedValue(true); - keyService.getUserKeyWithLegacySupport.mockResolvedValue( - new SymmetricCryptoKey(makeStaticByteArray(32)) as UserKey, - ); + keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); encryptService.decryptToUtf8.mockResolvedValue("DEC"); folderService = new FolderService( @@ -57,10 +57,53 @@ describe("Folder Service", () => { stateProvider, ); - folderState = stateProvider.activeUser.getFake(FOLDER_ENCRYPTED_FOLDERS); + folderState = stateProvider.singleUser.getFake(mockUserId, FOLDER_ENCRYPTED_FOLDERS); // Initial state - folderState.nextState({ "1": folderData("1", "test") }); + folderState.nextState({ "1": folderData("1") }); + }); + + describe("folders$", () => { + it("emits encrypted folders from state", async () => { + const folder1 = folderData("1"); + const folder2 = folderData("2"); + + await stateProvider.setUserState( + FOLDER_ENCRYPTED_FOLDERS, + Object.fromEntries([folder1, folder2].map((f) => [f.id, f])), + mockUserId, + ); + + const result = await firstValueFrom(folderService.folders$(mockUserId)); + + expect(result.length).toBe(2); + expect(result).toIncludeAllPartialMembers([ + { id: "1", name: makeEncString("ENC_STRING_1") }, + { id: "2", name: makeEncString("ENC_STRING_2") }, + ]); + }); + }); + + describe("folderView$", () => { + it("emits decrypted folders from state", async () => { + const folder1 = folderData("1"); + const folder2 = folderData("2"); + + await stateProvider.setUserState( + FOLDER_ENCRYPTED_FOLDERS, + Object.fromEntries([folder1, folder2].map((f) => [f.id, f])), + mockUserId, + ); + + const result = await firstValueFrom(folderService.folderViews$(mockUserId)); + + expect(result.length).toBe(3); + expect(result).toIncludeAllPartialMembers([ + { id: "1", name: "DEC" }, + { id: "2", name: "DEC" }, + { name: "No Folder" }, + ]); + }); }); it("encrypt", async () => { @@ -83,105 +126,83 @@ describe("Folder Service", () => { describe("get", () => { it("exists", async () => { - const result = await folderService.get("1"); + const result = await folderService.get("1", mockUserId); expect(result).toEqual({ id: "1", - name: { - encryptedString: "test", - encryptionType: 0, - }, + name: makeEncString("ENC_STRING_" + 1), revisionDate: null, }); }); it("not exists", async () => { - const result = await folderService.get("2"); + const result = await folderService.get("2", mockUserId); expect(result).toBe(undefined); }); }); it("upsert", async () => { - await folderService.upsert(folderData("2", "test 2")); + await folderService.upsert(folderData("2"), mockUserId); - expect(await firstValueFrom(folderService.folders$)).toEqual([ + expect(await firstValueFrom(folderService.folders$(mockUserId))).toEqual([ { id: "1", - name: { - encryptedString: "test", - encryptionType: 0, - }, + name: makeEncString("ENC_STRING_" + 1), revisionDate: null, }, { id: "2", - name: { - encryptedString: "test 2", - encryptionType: 0, - }, + name: makeEncString("ENC_STRING_" + 2), revisionDate: null, }, ]); }); it("replace", async () => { - await folderService.replace({ "2": folderData("2", "test 2") }, mockUserId); + await folderService.replace({ "4": folderData("4") }, mockUserId); - expect(await firstValueFrom(folderService.folders$)).toEqual([ + expect(await firstValueFrom(folderService.folders$(mockUserId))).toEqual([ { - id: "2", - name: { - encryptedString: "test 2", - encryptionType: 0, - }, + id: "4", + name: makeEncString("ENC_STRING_" + 4), revisionDate: null, }, ]); }); it("delete", async () => { - await folderService.delete("1"); + await folderService.delete("1", mockUserId); - expect((await firstValueFrom(folderService.folders$)).length).toBe(0); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(0); }); - it("clearCache", async () => { - await folderService.clearCache(); + describe("clearDecryptedFolderState", () => { + it("null userId", async () => { + await expect(folderService.clearDecryptedFolderState(null)).rejects.toThrow( + "User ID is required.", + ); + }); + + it("userId provided", async () => { + await folderService.clearDecryptedFolderState(mockUserId); - expect((await firstValueFrom(folderService.folders$)).length).toBe(1); - expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(1); + expect( + (await firstValueFrom(stateProvider.getUserState$(FOLDER_DECRYPTED_FOLDERS, mockUserId))) + .length, + ).toBe(0); + }); }); - describe("clear", () => { - it("null userId", async () => { - await folderService.clear(); + it("clear", async () => { + await folderService.clear(mockUserId); - expect((await firstValueFrom(folderService.folders$)).length).toBe(0); - expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0); - }); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(0); - /** - * TODO: Fix this test to address the problem where the fakes for the active user state is not - * updated as expected - */ - // it("matching userId", async () => { - // stateService.getUserId.mockResolvedValue("1"); - // await folderService.clear("1" as UserId); - - // expect((await firstValueFrom(folderService.folders$)).length).toBe(0); - // }); - - /** - * TODO: Fix this test to address the problem where the fakes for the active user state is not - * updated as expected - */ - // it("mismatching userId", async () => { - // await folderService.clear("12" as UserId); - - // expect((await firstValueFrom(folderService.folders$)).length).toBe(1); - // expect((await firstValueFrom(folderService.folderViews$)).length).toBe(2); - // }); + const folderViews = await firstValueFrom(folderService.folderViews$(mockUserId)); + expect(folderViews.length).toBe(1); + expect(folderViews[0].id).toBeNull(); // Should be the "No Folder" folder }); describe("getRotatedData", () => { @@ -207,10 +228,10 @@ describe("Folder Service", () => { }); }); - function folderData(id: string, name: string) { + function folderData(id: string) { const data = new FolderData({} as any); data.id = id; - data.name = name; + data.name = makeEncString("ENC_STRING_" + data.id).encryptedString; return data; } diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 2a76e82f3b7..c21a92fd894 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -1,12 +1,15 @@ -import { Observable, firstValueFrom, map, shareReplay } from "rxjs"; - -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; - +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; + +import { EncryptService } from ".././../../platform/abstractions/encrypt.service"; +import { Utils } from ".././../../platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { ActiveUserState, DerivedState, StateProvider } from "../../../platform/state"; +import { StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { CipherService } from "../../../vault/abstractions/cipher.service"; @@ -19,11 +22,18 @@ import { FolderWithIdRequest } from "../../models/request/folder-with-id.request import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; export class FolderService implements InternalFolderServiceAbstraction { - folders$: Observable; - folderViews$: Observable; + /** + * Ensures we reuse the same observable stream for each userId rather than + * creating a new one on each folderViews$ call. + */ + private folderViewCache = new Map>(); - private encryptedFoldersState: ActiveUserState>; - private decryptedFoldersState: DerivedState; + /** + * Used to force the folderviews$ Observable to re-emit with a provided value. + * Required because shareReplay with refCount: false maintains last emission. + * Used during cleanup to force emit empty arrays, ensuring stale data isn't retained. + */ + private forceFolderViews: Record> = {}; constructor( private keyService: KeyService, @@ -31,23 +41,44 @@ export class FolderService implements InternalFolderServiceAbstraction { private i18nService: I18nService, private cipherService: CipherService, private stateProvider: StateProvider, - ) { - this.encryptedFoldersState = this.stateProvider.getActive(FOLDER_ENCRYPTED_FOLDERS); - this.decryptedFoldersState = this.stateProvider.getDerived( - this.encryptedFoldersState.state$, - FOLDER_DECRYPTED_FOLDERS, - { folderService: this, keyService: this.keyService }, - ); + ) {} - this.folders$ = this.encryptedFoldersState.state$.pipe( - map((folderData) => Object.values(folderData).map((f) => new Folder(f))), - ); + folders$(userId: UserId): Observable { + return this.encryptedFoldersState(userId).state$.pipe( + map((folders) => { + if (folders == null) { + return []; + } - this.folderViews$ = this.decryptedFoldersState.state$; + return Object.values(folders).map((f) => new Folder(f)); + }), + ); } - async clearCache(): Promise { - await this.decryptedFoldersState.forceValue([]); + /** + * Returns an Observable of decrypted folder views for the given userId. + * Uses folderViewCache to maintain a single Observable instance per user, + * combining normal folder state updates with forced updates. + */ + folderViews$(userId: UserId): Observable { + if (!this.folderViewCache.has(userId)) { + if (!this.forceFolderViews[userId]) { + this.forceFolderViews[userId] = new Subject(); + } + + const observable = merge( + this.forceFolderViews[userId], + this.encryptedFoldersState(userId).state$.pipe( + switchMap((folderData) => { + return this.decryptFolders(userId, folderData); + }), + ), + ).pipe(shareReplay({ refCount: false, bufferSize: 1 })); + + this.folderViewCache.set(userId, observable); + } + + return this.folderViewCache.get(userId); } // TODO: This should be moved to EncryptService or something @@ -58,29 +89,29 @@ export class FolderService implements InternalFolderServiceAbstraction { return folder; } - async get(id: string): Promise { - const folders = await firstValueFrom(this.folders$); + async get(id: string, userId: UserId): Promise { + const folders = await firstValueFrom(this.folders$(userId)); return folders.find((folder) => folder.id === id); } - getDecrypted$(id: string): Observable { - return this.folderViews$.pipe( + getDecrypted$(id: string, userId: UserId): Observable { + return this.folderViews$(userId).pipe( map((folders) => folders.find((folder) => folder.id === id)), shareReplay({ refCount: true, bufferSize: 1 }), ); } - async getAllFromState(): Promise { - return await firstValueFrom(this.folders$); + async getAllFromState(userId: UserId): Promise { + return await firstValueFrom(this.folders$(userId)); } /** * @deprecated For the CLI only * @param id id of the folder */ - async getFromState(id: string): Promise { - const folder = await this.get(id); + async getFromState(id: string, userId: UserId): Promise { + const folder = await this.get(id, userId); if (!folder) { return null; } @@ -91,12 +122,13 @@ export class FolderService implements InternalFolderServiceAbstraction { /** * @deprecated Only use in CLI! */ - async getAllDecryptedFromState(): Promise { - return await firstValueFrom(this.folderViews$); + async getAllDecryptedFromState(userId: UserId): Promise { + return await firstValueFrom(this.folderViews$(userId)); } - async upsert(folderData: FolderData | FolderData[]): Promise { - await this.encryptedFoldersState.update((folders) => { + async upsert(folderData: FolderData | FolderData[], userId: UserId): Promise { + await this.clearDecryptedFolderState(userId); + await this.encryptedFoldersState(userId).update((folders) => { if (folders == null) { folders = {}; } @@ -118,24 +150,31 @@ export class FolderService implements InternalFolderServiceAbstraction { if (!folders) { return; } - + await this.clearDecryptedFolderState(userId); await this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS).update(() => { const newFolders: Record = { ...folders }; return newFolders; }); } - async clear(userId?: UserId): Promise { + async clearDecryptedFolderState(userId: UserId): Promise { if (userId == null) { - await this.encryptedFoldersState.update(() => ({})); - await this.decryptedFoldersState.forceValue([]); - } else { - await this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS).update(() => ({})); + throw new Error("User ID is required."); } + + await this.setDecryptedFolders([], userId); } - async delete(id: string | string[]): Promise { - await this.encryptedFoldersState.update((folders) => { + async clear(userId: UserId): Promise { + this.forceFolderViews[userId]?.next([]); + + await this.encryptedFoldersState(userId).update(() => ({})); + await this.clearDecryptedFolderState(userId); + } + + async delete(id: string | string[], userId: UserId): Promise { + await this.clearDecryptedFolderState(userId); + await this.encryptedFoldersState(userId).update((folders) => { if (folders == null) { return; } @@ -162,25 +201,11 @@ export class FolderService implements InternalFolderServiceAbstraction { } } if (updates.length > 0) { - // 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.cipherService.upsert(updates.map((c) => c.toCipherData())); + await this.cipherService.upsert(updates.map((c) => c.toCipherData())); } } } - async decryptFolders(folders: Folder[]) { - const decryptFolderPromises = folders.map((f) => f.decrypt()); - const decryptedFolders = await Promise.all(decryptFolderPromises); - - decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name")); - - const noneFolder = new FolderView(); - noneFolder.name = this.i18nService.t("noneFolder"); - decryptedFolders.push(noneFolder); - return decryptedFolders; - } - async getRotatedData( originalUserKey: UserKey, newUserKey: UserKey, @@ -191,7 +216,7 @@ export class FolderService implements InternalFolderServiceAbstraction { } let encryptedFolders: FolderWithIdRequest[] = []; - const folders = await firstValueFrom(this.folderViews$); + const folders = await firstValueFrom(this.folderViews$(userId)); if (!folders) { return encryptedFolders; } @@ -203,4 +228,63 @@ export class FolderService implements InternalFolderServiceAbstraction { ); return encryptedFolders; } + + /** + * Decrypts the folders for a user. + * @param userId the user id + * @param folderData encrypted folders + * @returns a list of decrypted folders + */ + private async decryptFolders( + userId: UserId, + folderData: Record, + ): Promise { + // Check if the decrypted folders are already cached + const decrypted = await firstValueFrom( + this.stateProvider.getUser(userId, FOLDER_DECRYPTED_FOLDERS).state$, + ); + if (decrypted?.length) { + return decrypted; + } + + if (folderData == null) { + return []; + } + + const folders = Object.values(folderData).map((f) => new Folder(f)); + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (!userKey) { + return []; + } + + const decryptFolderPromises = folders.map((f) => + f.decryptWithKey(userKey, this.encryptService), + ); + const decryptedFolders = await Promise.all(decryptFolderPromises); + decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name")); + + const noneFolder = new FolderView(); + noneFolder.name = this.i18nService.t("noneFolder"); + decryptedFolders.push(noneFolder); + + // Cache the decrypted folders + await this.setDecryptedFolders(decryptedFolders, userId); + return decryptedFolders; + } + + /** + * @returns a SingleUserState for the encrypted folders. + */ + private encryptedFoldersState(userId: UserId) { + return this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS); + } + + /** + * Sets the decrypted folders state for a user. + * @param folders the decrypted folders + * @param userId the user id + */ + private async setDecryptedFolders(folders: FolderView[], userId: UserId): Promise { + await this.stateProvider.setUserState(FOLDER_DECRYPTED_FOLDERS, folders, userId); + } } diff --git a/libs/common/src/vault/services/key-state/ciphers.state.ts b/libs/common/src/vault/services/key-state/ciphers.state.ts index ff6b4fb051b..d85e625243e 100644 --- a/libs/common/src/vault/services/key-state/ciphers.state.ts +++ b/libs/common/src/vault/services/key-state/ciphers.state.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Jsonify } from "type-fest"; import { @@ -26,6 +28,15 @@ export const DECRYPTED_CIPHERS = UserKeyDefinition.record( }, ); +export const FAILED_DECRYPTED_CIPHERS = UserKeyDefinition.array( + CIPHERS_MEMORY, + "failedDecryptedCiphers", + { + deserializer: (cipher: Jsonify) => CipherView.fromJSON(cipher), + clearOn: ["logout", "lock"], + }, +); + export const LOCAL_DATA_KEY = new UserKeyDefinition>( CIPHERS_DISK_LOCAL, "localData", diff --git a/libs/common/src/vault/services/key-state/folder.state.spec.ts b/libs/common/src/vault/services/key-state/folder.state.spec.ts index ece66b5d451..217a200ea88 100644 --- a/libs/common/src/vault/services/key-state/folder.state.spec.ts +++ b/libs/common/src/vault/services/key-state/folder.state.spec.ts @@ -1,11 +1,3 @@ -import { mock } from "jest-mock-extended"; - -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { FolderService } from "../../abstractions/folder/folder.service.abstraction"; -import { FolderData } from "../../models/data/folder.data"; -import { Folder } from "../../models/domain/folder"; -import { FolderView } from "../../models/view/folder.view"; - import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "./folder.state"; describe("encrypted folders", () => { @@ -31,48 +23,32 @@ describe("encrypted folders", () => { }); describe("derived decrypted folders", () => { - const keyService = mock(); - const folderService = mock(); const sut = FOLDER_DECRYPTED_FOLDERS; - let data: FolderData; - - beforeEach(() => { - data = { - id: "id", - name: "encName", - revisionDate: "2024-01-31T12:00:00.000Z", - }; - }); - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should deserialize encrypted folders", async () => { - const inputObj = [data]; + it("should deserialize decrypted folders", async () => { + const inputObj = [ + { + id: "id", + name: "encName", + revisionDate: "2024-01-31T12:00:00.000Z", + }, + ]; - const expectedFolderView = { - id: "id", - name: "encName", - revisionDate: new Date("2024-01-31T12:00:00.000Z"), - }; + const expectedFolderView = [ + { + id: "id", + name: "encName", + revisionDate: new Date("2024-01-31T12:00:00.000Z"), + }, + ]; - const result = sut.deserialize(JSON.parse(JSON.stringify(inputObj))); + const result = sut.deserializer(JSON.parse(JSON.stringify(inputObj))); - expect(result).toEqual([expectedFolderView]); + expect(result).toEqual(expectedFolderView); }); - it("should derive encrypted folders", async () => { - const folderViewMock = new FolderView(new Folder(data)); - keyService.hasUserKey.mockResolvedValue(true); - folderService.decryptFolders.mockResolvedValue([folderViewMock]); - - const encryptedFoldersState = { id: data }; - const derivedStateResult = await sut.derive(encryptedFoldersState, { - folderService, - keyService, - }); - - expect(derivedStateResult).toEqual([folderViewMock]); + it("should handle null input", async () => { + const result = sut.deserializer(null); + expect(result).toEqual([]); }); }); diff --git a/libs/common/src/vault/services/key-state/folder.state.ts b/libs/common/src/vault/services/key-state/folder.state.ts index 7262d72d58e..99ad8e5ae35 100644 --- a/libs/common/src/vault/services/key-state/folder.state.ts +++ b/libs/common/src/vault/services/key-state/folder.state.ts @@ -1,34 +1,23 @@ import { Jsonify } from "type-fest"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { DeriveDefinition, FOLDER_DISK, UserKeyDefinition } from "../../../platform/state"; -import { FolderService } from "../../abstractions/folder/folder.service.abstraction"; +import { FOLDER_DISK, FOLDER_MEMORY, UserKeyDefinition } from "../../../platform/state"; import { FolderData } from "../../models/data/folder.data"; -import { Folder } from "../../models/domain/folder"; import { FolderView } from "../../models/view/folder.view"; export const FOLDER_ENCRYPTED_FOLDERS = UserKeyDefinition.record( FOLDER_DISK, - "folders", + "folder", { deserializer: (obj: Jsonify) => FolderData.fromJSON(obj), clearOn: ["logout"], }, ); -export const FOLDER_DECRYPTED_FOLDERS = DeriveDefinition.from< - Record, - FolderView[], - { folderService: FolderService; keyService: KeyService } ->(FOLDER_ENCRYPTED_FOLDERS, { - deserializer: (obj) => obj.map((f) => FolderView.fromJSON(f)), - derive: async (from, { folderService, keyService }) => { - const folders = Object.values(from || {}).map((f) => new Folder(f)); - - if (await keyService.hasUserKey()) { - return await folderService.decryptFolders(folders); - } else { - return []; - } +export const FOLDER_DECRYPTED_FOLDERS = new UserKeyDefinition( + FOLDER_MEMORY, + "decryptedFolders", + { + deserializer: (obj: Jsonify) => obj?.map((f) => FolderView.fromJSON(f)) ?? [], + clearOn: ["logout", "lock"], }, -}); +); diff --git a/libs/common/src/vault/services/key-state/vault-settings.state.ts b/libs/common/src/vault/services/key-state/vault-settings.state.ts index 21364bbbf8e..35bb776cc96 100644 --- a/libs/common/src/vault/services/key-state/vault-settings.state.ts +++ b/libs/common/src/vault/services/key-state/vault-settings.state.ts @@ -25,3 +25,12 @@ export const SHOW_IDENTITIES_CURRENT_TAB = new UserKeyDefinition( clearOn: [], // do not clear user settings }, ); + +export const CLICK_ITEMS_AUTOFILL_VAULT_VIEW = new UserKeyDefinition( + VAULT_SETTINGS_DISK, + "clickItemsToAutofillOnVaultView", + { + deserializer: (obj) => obj, + clearOn: [], // do not clear user settings + }, +); diff --git a/libs/common/src/vault/services/totp.service.ts b/libs/common/src/vault/services/totp.service.ts index 1456b383a20..b66e4a1bcf0 100644 --- a/libs/common/src/vault/services/totp.service.ts +++ b/libs/common/src/vault/services/totp.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; import { LogService } from "../../platform/abstractions/log.service"; import { Utils } from "../../platform/misc/utils"; diff --git a/libs/common/src/vault/services/vault-settings/vault-settings.service.ts b/libs/common/src/vault/services/vault-settings/vault-settings.service.ts index 39b96318217..85ab3914158 100644 --- a/libs/common/src/vault/services/vault-settings/vault-settings.service.ts +++ b/libs/common/src/vault/services/vault-settings/vault-settings.service.ts @@ -6,6 +6,7 @@ import { SHOW_CARDS_CURRENT_TAB, SHOW_IDENTITIES_CURRENT_TAB, USER_ENABLE_PASSKEYS, + CLICK_ITEMS_AUTOFILL_VAULT_VIEW, } from "../key-state/vault-settings.state"; /** @@ -39,6 +40,14 @@ export class VaultSettingsService implements VaultSettingsServiceAbstraction { readonly showIdentitiesCurrentTab$: Observable = this.showIdentitiesCurrentTabState.state$.pipe(map((x) => x ?? true)); + private clickItemsToAutofillVaultViewState: ActiveUserState = + this.stateProvider.getActive(CLICK_ITEMS_AUTOFILL_VAULT_VIEW); + /** + * {@link VaultSettingsServiceAbstraction.clickItemsToAutofillVaultView$$} + */ + readonly clickItemsToAutofillVaultView$: Observable = + this.clickItemsToAutofillVaultViewState.state$.pipe(map((x) => x ?? false)); + constructor(private stateProvider: StateProvider) {} /** @@ -55,6 +64,13 @@ export class VaultSettingsService implements VaultSettingsServiceAbstraction { await this.showIdentitiesCurrentTabState.update(() => value); } + /** + * {@link VaultSettingsServiceAbstraction.setClickItemsToAutofillVaultView} + */ + async setClickItemsToAutofillVaultView(value: boolean): Promise { + await this.clickItemsToAutofillVaultViewState.update(() => value); + } + /** * {@link VaultSettingsServiceAbstraction.setEnablePasskeys} */ diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index 99c58f3cf24..c28b60e28f8 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -1,12 +1,16 @@ { - "extends": "../shared/tsconfig.libs", - "include": [ - "src", - "spec", - "./custom-matchers.d.ts", - "../key-management/src/key.service.spec.ts", - "../key-management/src/key.service.ts", - "../key-management/src/abstractions/key.service.ts" - ], + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"] + } + }, + "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/components/src/a11y/a11y-cell.directive.ts b/libs/components/src/a11y/a11y-cell.directive.ts index fdd75c076f9..c9a8fdda255 100644 --- a/libs/components/src/a11y/a11y-cell.directive.ts +++ b/libs/components/src/a11y/a11y-cell.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { ContentChild, Directive, ElementRef, HostBinding } from "@angular/core"; import { FocusableElement } from "../shared/focusable-element"; diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index c632376f4fc..ef7ba68b65c 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AfterViewInit, ContentChildren, @@ -84,6 +86,8 @@ export class A11yGridDirective implements AfterViewInit { }); this.getActiveCellContent().tabIndex = 0; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // eslint-disable-next-line no-console console.error("Unable to initialize grid"); diff --git a/libs/components/src/a11y/a11y-row.directive.ts b/libs/components/src/a11y/a11y-row.directive.ts index e062eb2b5a9..7e0431d17e2 100644 --- a/libs/components/src/a11y/a11y-row.directive.ts +++ b/libs/components/src/a11y/a11y-row.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AfterViewInit, ContentChildren, diff --git a/libs/components/src/async-actions/async-actions.module.ts b/libs/components/src/async-actions/async-actions.module.ts index 8ff1deb2784..bff4286f890 100644 --- a/libs/components/src/async-actions/async-actions.module.ts +++ b/libs/components/src/async-actions/async-actions.module.ts @@ -1,14 +1,11 @@ import { NgModule } from "@angular/core"; -import { SharedModule } from "../shared"; - import { BitActionDirective } from "./bit-action.directive"; import { BitSubmitDirective } from "./bit-submit.directive"; import { BitFormButtonDirective } from "./form-button.directive"; @NgModule({ - imports: [SharedModule], - declarations: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective], + imports: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective], exports: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective], }) export class AsyncActionsModule {} diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index 28077bf98d4..3e793ae2ecd 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, HostListener, Input, OnDestroy, Optional } from "@angular/core"; import { BehaviorSubject, finalize, Subject, takeUntil, tap } from "rxjs"; @@ -13,6 +15,7 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct */ @Directive({ selector: "[bitAction]", + standalone: true, }) export class BitActionDirective implements OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/async-actions/bit-submit.directive.ts b/libs/components/src/async-actions/bit-submit.directive.ts index 05f31f95c0e..a38e76aaca6 100644 --- a/libs/components/src/async-actions/bit-submit.directive.ts +++ b/libs/components/src/async-actions/bit-submit.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, Input, OnDestroy, OnInit, Optional } from "@angular/core"; import { FormGroupDirective } from "@angular/forms"; import { BehaviorSubject, catchError, filter, of, Subject, switchMap, takeUntil } from "rxjs"; @@ -12,6 +14,7 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct */ @Directive({ selector: "[formGroup][bitSubmit]", + standalone: true, }) export class BitSubmitDirective implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index 4e0facf17b2..7c92865b984 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, Input, OnDestroy, Optional } from "@angular/core"; import { Subject, takeUntil } from "rxjs"; @@ -23,6 +25,7 @@ import { BitSubmitDirective } from "./bit-submit.directive"; */ @Directive({ selector: "button[bitFormButton]", + standalone: true, }) export class BitFormButtonDirective implements OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index ec6005dd607..7ddb32da281 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -5,6 +5,8 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { delay, of } from "rxjs"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; import { ButtonModule } from "../button"; @@ -109,20 +111,17 @@ export default { title: "Component Library/Async Actions/In Forms", decorators: [ moduleMetadata({ - declarations: [ + declarations: [PromiseExampleComponent, ObservableExampleComponent], + imports: [ BitSubmitDirective, BitFormButtonDirective, - PromiseExampleComponent, - ObservableExampleComponent, - BitActionDirective, - ], - imports: [ FormsModule, ReactiveFormsModule, FormFieldModule, InputModule, ButtonModule, IconButtonModule, + BitActionDirective, ], providers: [ { diff --git a/libs/components/src/async-actions/standalone.stories.ts b/libs/components/src/async-actions/standalone.stories.ts index 5e15135dc5d..f658dfb0f01 100644 --- a/libs/components/src/async-actions/standalone.stories.ts +++ b/libs/components/src/async-actions/standalone.stories.ts @@ -56,12 +56,11 @@ export default { decorators: [ moduleMetadata({ declarations: [ - BitActionDirective, PromiseExampleComponent, ObservableExampleComponent, RejectedPromiseExampleComponent, ], - imports: [ButtonModule, IconButtonModule], + imports: [ButtonModule, IconButtonModule, BitActionDirective], providers: [ { provide: ValidationService, diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index b19506952d9..76ff702e88b 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -1,3 +1,6 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { NgIf, NgClass } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; @@ -16,6 +19,8 @@ const SizeClasses: Record = { @Component({ selector: "bit-avatar", template: ``, + standalone: true, + imports: [NgIf, NgClass], }) export class AvatarComponent implements OnChanges { @Input() border = false; @@ -116,7 +121,7 @@ export class AvatarComponent implements OnChanges { textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); textTag.setAttribute( "font-family", - '"Open Sans","Helvetica Neue",Helvetica,Arial,' + + '"DM Sans","Helvetica Neue",Helvetica,Arial,' + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', ); // Warning do not use innerHTML here, characters are user provided diff --git a/libs/components/src/avatar/avatar.module.ts b/libs/components/src/avatar/avatar.module.ts index ea78ff3a1d2..4ef0978cbec 100644 --- a/libs/components/src/avatar/avatar.module.ts +++ b/libs/components/src/avatar/avatar.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { AvatarComponent } from "./avatar.component"; @NgModule({ - imports: [CommonModule], + imports: [AvatarComponent], exports: [AvatarComponent], - declarations: [AvatarComponent], }) export class AvatarModule {} diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index 5ec0d7dc4ce..ac8cb3281ab 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -1,10 +1,16 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; -import { BadgeVariant } from "../badge"; +import { BadgeModule, BadgeVariant } from "../badge"; +import { I18nPipe } from "../shared/i18n.pipe"; @Component({ selector: "bit-badge-list", templateUrl: "badge-list.component.html", + standalone: true, + imports: [CommonModule, BadgeModule, I18nPipe], }) export class BadgeListComponent implements OnChanges { private _maxItems: number; diff --git a/libs/components/src/badge-list/badge-list.module.ts b/libs/components/src/badge-list/badge-list.module.ts index d2a4ce211b1..9359fe2c5c5 100644 --- a/libs/components/src/badge-list/badge-list.module.ts +++ b/libs/components/src/badge-list/badge-list.module.ts @@ -1,13 +1,9 @@ import { NgModule } from "@angular/core"; -import { BadgeModule } from "../badge"; -import { SharedModule } from "../shared"; - import { BadgeListComponent } from "./badge-list.component"; @NgModule({ - imports: [SharedModule, BadgeModule], + imports: [BadgeListComponent], exports: [BadgeListComponent], - declarations: [BadgeListComponent], }) export class BadgeListModule {} diff --git a/libs/components/src/badge-list/badge-list.stories.ts b/libs/components/src/badge-list/badge-list.stories.ts index 24024cb6bbd..ede005f6fd6 100644 --- a/libs/components/src/badge-list/badge-list.stories.ts +++ b/libs/components/src/badge-list/badge-list.stories.ts @@ -13,8 +13,7 @@ export default { component: BadgeListComponent, decorators: [ moduleMetadata({ - imports: [SharedModule, BadgeModule], - declarations: [BadgeListComponent], + imports: [SharedModule, BadgeModule, BadgeListComponent], providers: [ { provide: I18nService, diff --git a/libs/components/src/badge/badge.component.html b/libs/components/src/badge/badge.component.html new file mode 100644 index 00000000000..6f586ec3523 --- /dev/null +++ b/libs/components/src/badge/badge.component.html @@ -0,0 +1,3 @@ + + + diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts new file mode 100644 index 00000000000..0f8a64ee998 --- /dev/null +++ b/libs/components/src/badge/badge.component.ts @@ -0,0 +1,104 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; +import { Component, ElementRef, HostBinding, Input } from "@angular/core"; + +import { FocusableElement } from "../shared/focusable-element"; + +export type BadgeVariant = "primary" | "secondary" | "success" | "danger" | "warning" | "info"; + +const styles: Record = { + primary: ["tw-bg-primary-100", "tw-border-primary-700", "!tw-text-primary-700"], + secondary: ["tw-bg-secondary-100", "tw-border-secondary-700", "!tw-text-secondary-700"], + success: ["tw-bg-success-100", "tw-border-success-700", "!tw-text-success-700"], + danger: ["tw-bg-danger-100", "tw-border-danger-700", "!tw-text-danger-700"], + warning: ["tw-bg-warning-100", "tw-border-warning-700", "!tw-text-warning-700"], + info: ["tw-bg-info-100", "tw-border-info-700", "!tw-text-info-700"], +}; + +const hoverStyles: Record = { + primary: ["hover:tw-bg-primary-600", "hover:tw-border-primary-600", "hover:!tw-text-contrast"], + secondary: [ + "hover:tw-bg-secondary-600", + "hover:tw-border-secondary-600", + "hover:!tw-text-contrast", + ], + success: ["hover:tw-bg-success-600", "hover:tw-border-success-600", "hover:!tw-text-contrast"], + danger: ["hover:tw-bg-danger-600", "hover:tw-border-danger-600", "hover:!tw-text-contrast"], + warning: ["hover:tw-bg-warning-600", "hover:tw-border-warning-600", "hover:!tw-text-black"], + info: ["hover:tw-bg-info-600", "hover:tw-border-info-600", "hover:!tw-text-black"], +}; + +@Component({ + selector: "span[bitBadge], a[bitBadge], button[bitBadge]", + providers: [{ provide: FocusableElement, useExisting: BadgeComponent }], + imports: [CommonModule], + templateUrl: "badge.component.html", + standalone: true, +}) +export class BadgeComponent implements FocusableElement { + @HostBinding("class") get classList() { + return [ + "tw-inline-block", + "tw-py-1", + "tw-px-2", + "tw-font-medium", + "tw-text-center", + "tw-align-text-top", + "tw-rounded-full", + "tw-border-[0.5px]", + "tw-border-solid", + "tw-box-border", + "tw-whitespace-nowrap", + "tw-text-xs", + "hover:tw-no-underline", + "focus-visible:tw-outline-none", + "focus-visible:tw-ring-2", + "focus-visible:tw-ring-offset-2", + "focus-visible:tw-ring-primary-600", + "disabled:tw-bg-secondary-300", + "disabled:hover:tw-bg-secondary-300", + "disabled:tw-border-secondary-300", + "disabled:hover:tw-border-secondary-300", + "disabled:!tw-text-muted", + "disabled:hover:!tw-text-muted", + "disabled:tw-cursor-not-allowed", + ] + .concat(styles[this.variant]) + .concat(this.hasHoverEffects ? [...hoverStyles[this.variant], "tw-min-w-10"] : []) + .concat(this.truncate ? this.maxWidthClass : []); + } + @HostBinding("attr.title") get titleAttr() { + if (this.title !== undefined) { + return this.title; + } + return this.truncate ? this.el.nativeElement.textContent.trim() : null; + } + + /** + * Optional override for the automatic badge title when truncating. + */ + @Input() title?: string; + + /** + * Variant, sets the background color of the badge. + */ + @Input() variant: BadgeVariant = "primary"; + + /** + * Truncate long text + */ + @Input() truncate = true; + + @Input() maxWidthClass: `tw-max-w-${string}` = "tw-max-w-40"; + + getFocusTarget() { + return this.el.nativeElement; + } + + private hasHoverEffects = false; + + constructor(private el: ElementRef) { + this.hasHoverEffects = el?.nativeElement?.nodeName != "SPAN"; + } +} diff --git a/libs/components/src/badge/badge.directive.ts b/libs/components/src/badge/badge.directive.ts deleted file mode 100644 index 55977f10f90..00000000000 --- a/libs/components/src/badge/badge.directive.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Directive, ElementRef, HostBinding, Input } from "@angular/core"; - -import { FocusableElement } from "../shared/focusable-element"; - -export type BadgeVariant = "primary" | "secondary" | "success" | "danger" | "warning" | "info"; - -const styles: Record = { - primary: ["tw-bg-primary-600"], - secondary: ["tw-bg-text-muted"], - success: ["tw-bg-success-600"], - danger: ["tw-bg-danger-600"], - warning: ["tw-bg-warning-600"], - info: ["tw-bg-info-600"], -}; - -const hoverStyles: Record = { - primary: ["hover:tw-bg-primary-700"], - secondary: ["hover:tw-bg-secondary-700"], - success: ["hover:tw-bg-success-700"], - danger: ["hover:tw-bg-danger-700"], - warning: ["hover:tw-bg-warning-700"], - info: ["hover:tw-bg-info-700"], -}; - -@Directive({ - selector: "span[bitBadge], a[bitBadge], button[bitBadge]", - providers: [{ provide: FocusableElement, useExisting: BadgeDirective }], -}) -export class BadgeDirective implements FocusableElement { - @HostBinding("class") get classList() { - return [ - "tw-inline-block", - "tw-py-0.5", - "tw-px-1.5", - "tw-font-bold", - "tw-text-center", - "tw-align-text-top", - "!tw-text-contrast", - "tw-rounded", - "tw-border-none", - "tw-box-border", - "tw-whitespace-nowrap", - "tw-text-xs", - "hover:tw-no-underline", - "focus:tw-outline-none", - "focus:tw-ring", - "focus:tw-ring-offset-2", - "focus:tw-ring-primary-700", - ] - .concat(styles[this.variant]) - .concat(this.hasHoverEffects ? hoverStyles[this.variant] : []) - .concat(this.truncate ? ["tw-truncate", this.maxWidthClass] : []); - } - @HostBinding("attr.title") get titleAttr() { - if (this.title !== undefined) { - return this.title; - } - return this.truncate ? this.el.nativeElement.textContent.trim() : null; - } - - /** - * Optional override for the automatic badge title when truncating. - */ - @Input() title?: string; - - /** - * Variant, sets the background color of the badge. - */ - @Input() variant: BadgeVariant = "primary"; - - /** - * Truncate long text - */ - @Input() truncate = true; - - @Input() maxWidthClass: `tw-max-w-${string}` = "tw-max-w-40"; - - getFocusTarget() { - return this.el.nativeElement; - } - - private hasHoverEffects = false; - - constructor(private el: ElementRef) { - this.hasHoverEffects = el?.nativeElement?.nodeName != "SPAN"; - } -} diff --git a/libs/components/src/badge/badge.mdx b/libs/components/src/badge/badge.mdx index 3dd24598712..fb2dceb0fa8 100644 --- a/libs/components/src/badge/badge.mdx +++ b/libs/components/src/badge/badge.mdx @@ -10,12 +10,17 @@ import { BadgeModule } from "@bitwarden/components"; # Badge -The Badge directive can be used on a `` (non clickable events), or an `` or ` + + + + + +

+ Hover + + + + + + +

+ Focus Visible + + + + + + +

+ Disabled + + + + + + + `, + }), +}; export const Primary: Story = { render: (args) => ({ props: args, - template: ` + template: /*html*/ ` Span Badge containing lengthy text

Link
Badge diff --git a/libs/components/src/badge/index.ts b/libs/components/src/badge/index.ts index a8f5babda91..ae19f4df288 100644 --- a/libs/components/src/badge/index.ts +++ b/libs/components/src/badge/index.ts @@ -1,2 +1,2 @@ -export { BadgeDirective, BadgeVariant } from "./badge.directive"; +export { BadgeComponent, BadgeVariant } from "./badge.component"; export * from "./badge.module"; diff --git a/libs/components/src/banner/banner.component.html b/libs/components/src/banner/banner.component.html index 865aacd410a..566494eb64a 100644 --- a/libs/components/src/banner/banner.component.html +++ b/libs/components/src/banner/banner.component.html @@ -1,18 +1,21 @@
- + + + + `, }), diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index 82803f25153..ce18bde171f 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -1,9 +1,14 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { NgIf } from "@angular/common"; import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core"; import { QueryParamsHandling } from "@angular/router"; @Component({ selector: "bit-breadcrumb", templateUrl: "./breadcrumb.component.html", + standalone: true, + imports: [NgIf], }) export class BreadcrumbComponent { @Input() diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index 64ca8146c80..6e8fbf5c25a 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -1,10 +1,18 @@ +import { CommonModule } from "@angular/common"; import { Component, ContentChildren, Input, QueryList } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { IconButtonModule } from "../icon-button"; +import { LinkModule } from "../link"; +import { MenuModule } from "../menu"; import { BreadcrumbComponent } from "./breadcrumb.component"; @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", + standalone: true, + imports: [CommonModule, LinkModule, RouterModule, IconButtonModule, MenuModule], }) export class BreadcrumbsComponent { @Input() diff --git a/libs/components/src/breadcrumbs/breadcrumbs.module.ts b/libs/components/src/breadcrumbs/breadcrumbs.module.ts index 0812b552f9a..89b57fd19b5 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.module.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.module.ts @@ -1,17 +1,10 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { RouterModule } from "@angular/router"; - -import { IconButtonModule } from "../icon-button"; -import { LinkModule } from "../link"; -import { MenuModule } from "../menu"; import { BreadcrumbComponent } from "./breadcrumb.component"; import { BreadcrumbsComponent } from "./breadcrumbs.component"; @NgModule({ - imports: [CommonModule, LinkModule, IconButtonModule, MenuModule, RouterModule], - declarations: [BreadcrumbsComponent, BreadcrumbComponent], + imports: [BreadcrumbsComponent, BreadcrumbComponent], exports: [BreadcrumbsComponent, BreadcrumbComponent], }) export class BreadcrumbsModule {} diff --git a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts index 300369f2454..9c8ccbccd3f 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts @@ -25,8 +25,7 @@ export default { component: BreadcrumbsComponent, decorators: [ moduleMetadata({ - declarations: [BreadcrumbComponent], - imports: [LinkModule, MenuModule, IconButtonModule, RouterModule], + imports: [LinkModule, MenuModule, IconButtonModule, RouterModule, BreadcrumbComponent], }), applicationConfig({ providers: [ diff --git a/libs/components/src/button/button.component.spec.ts b/libs/components/src/button/button.component.spec.ts index a75ac400a96..d63f611a5f8 100644 --- a/libs/components/src/button/button.component.spec.ts +++ b/libs/components/src/button/button.component.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, DebugElement } from "@angular/core"; import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; @@ -27,57 +29,6 @@ describe("Button", () => { linkDebugElement = fixture.debugElement.query(By.css("a")); })); - it("should apply classes based on type", () => { - testAppComponent.buttonType = "primary"; - fixture.detectChanges(); - expect(buttonDebugElement.nativeElement.classList.contains("tw-bg-primary-600")).toBe(true); - expect(linkDebugElement.nativeElement.classList.contains("tw-bg-primary-600")).toBe(true); - - testAppComponent.buttonType = "secondary"; - fixture.detectChanges(); - expect(buttonDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true); - expect(linkDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true); - - testAppComponent.buttonType = "danger"; - fixture.detectChanges(); - expect(buttonDebugElement.nativeElement.classList.contains("tw-border-danger-600")).toBe(true); - expect(linkDebugElement.nativeElement.classList.contains("tw-border-danger-600")).toBe(true); - - testAppComponent.buttonType = "unstyled"; - fixture.detectChanges(); - expect( - Array.from(buttonDebugElement.nativeElement.classList).some((klass: string) => - klass.startsWith("tw-bg"), - ), - ).toBe(false); - expect( - Array.from(linkDebugElement.nativeElement.classList).some((klass: string) => - klass.startsWith("tw-bg"), - ), - ).toBe(false); - - testAppComponent.buttonType = null; - fixture.detectChanges(); - expect(buttonDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true); - expect(linkDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true); - }); - - it("should apply block when true and inline-block when false", () => { - testAppComponent.block = true; - fixture.detectChanges(); - expect(buttonDebugElement.nativeElement.classList.contains("tw-block")).toBe(true); - expect(linkDebugElement.nativeElement.classList.contains("tw-block")).toBe(true); - expect(buttonDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(false); - expect(linkDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(false); - - testAppComponent.block = false; - fixture.detectChanges(); - expect(buttonDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(true); - expect(linkDebugElement.nativeElement.classList.contains("tw-inline-block")).toBe(true); - expect(buttonDebugElement.nativeElement.classList.contains("tw-block")).toBe(false); - expect(linkDebugElement.nativeElement.classList.contains("tw-block")).toBe(false); - }); - it("should not be disabled when loading and disabled are false", () => { testAppComponent.loading = false; testAppComponent.disabled = false; diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index d8787c994f4..96311f91529 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -1,12 +1,15 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass } from "@angular/common"; import { Input, HostBinding, Component } from "@angular/core"; import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; const focusRing = [ - "focus-visible:tw-ring", + "focus-visible:tw-ring-2", "focus-visible:tw-ring-offset-2", - "focus-visible:tw-ring-primary-700", + "focus-visible:tw-ring-primary-600", "focus-visible:tw-z-10", ]; @@ -17,24 +20,15 @@ const buttonStyles: Record = { "!tw-text-contrast", "hover:tw-bg-primary-700", "hover:tw-border-primary-700", - "disabled:tw-bg-primary-600/60", - "disabled:tw-border-primary-600/60", - "disabled:!tw-text-contrast/60", - "disabled:tw-bg-clip-padding", - "disabled:tw-cursor-not-allowed", ...focusRing, ], secondary: [ "tw-bg-transparent", - "tw-border-text-muted", - "!tw-text-muted", - "hover:tw-bg-text-muted", - "hover:tw-border-text-muted", + "tw-border-primary-600", + "!tw-text-primary-600", + "hover:tw-bg-primary-600", + "hover:tw-border-primary-600", "hover:!tw-text-contrast", - "disabled:tw-bg-transparent", - "disabled:tw-border-text-muted/60", - "disabled:!tw-text-muted/60", - "disabled:tw-cursor-not-allowed", ...focusRing, ], danger: [ @@ -44,10 +38,6 @@ const buttonStyles: Record = { "hover:tw-bg-danger-600", "hover:tw-border-danger-600", "hover:!tw-text-contrast", - "disabled:tw-bg-transparent", - "disabled:tw-border-danger-600/60", - "disabled:!tw-text-danger/60", - "disabled:tw-cursor-not-allowed", ...focusRing, ], unstyled: [], @@ -57,6 +47,8 @@ const buttonStyles: Record = { selector: "button[bitButton], a[bitButton]", templateUrl: "button.component.html", providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }], + standalone: true, + imports: [NgClass], }) export class ButtonComponent implements ButtonLikeAbstraction { @HostBinding("class") get classList() { @@ -64,14 +56,22 @@ export class ButtonComponent implements ButtonLikeAbstraction { "tw-font-semibold", "tw-py-1.5", "tw-px-3", - "tw-rounded", + "tw-rounded-full", "tw-transition", - "tw-border", + "tw-border-2", "tw-border-solid", "tw-text-center", "tw-no-underline", "hover:tw-no-underline", "focus:tw-outline-none", + "disabled:tw-bg-secondary-300", + "disabled:hover:tw-bg-secondary-300", + "disabled:tw-border-secondary-300", + "disabled:hover:tw-border-secondary-300", + "disabled:!tw-text-muted", + "disabled:hover:!tw-text-muted", + "disabled:tw-cursor-not-allowed", + "disabled:hover:tw-no-underline", ] .concat(this.block ? ["tw-w-full", "tw-block"] : ["tw-inline-block"]) .concat(buttonStyles[this.buttonType ?? "secondary"]); @@ -99,8 +99,4 @@ export class ButtonComponent implements ButtonLikeAbstraction { @Input() loading = false; @Input() disabled = false; - - setButtonType(value: "primary" | "secondary" | "danger" | "unstyled") { - this.buttonType = value; - } } diff --git a/libs/components/src/button/button.mdx b/libs/components/src/button/button.mdx index 5ee709093e3..33e4aed19f7 100644 --- a/libs/components/src/button/button.mdx +++ b/libs/components/src/button/button.mdx @@ -64,9 +64,6 @@ Use the danger styling only in settings when the user may preform a permanent ac ## Disabled UI -Both the disabled and loading states use the default state’s color with a 60% opacity or -`tw-opacity-60`. - ## Block diff --git a/libs/components/src/button/button.module.ts b/libs/components/src/button/button.module.ts index 448e7c9dcf6..f1a86eff3ab 100644 --- a/libs/components/src/button/button.module.ts +++ b/libs/components/src/button/button.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { ButtonComponent } from "./button.component"; @NgModule({ - imports: [CommonModule], + imports: [ButtonComponent], exports: [ButtonComponent], - declarations: [ButtonComponent], }) export class ButtonModule {} diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 6923878b353..3654442801c 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -23,9 +23,21 @@ type Story = StoryObj; export const Primary: Story = { render: (args) => ({ props: args, - template: ` + template: /*html*/ ` +
- Link + + + + +
+ `, }), args: { @@ -93,13 +105,13 @@ export const DisabledWithAttribute: Story = { }; export const Block: Story = { - render: (args: ButtonComponent) => ({ + render: (args) => ({ props: args, template: ` [block]="true" Link - + block Link diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index 57da850d8bb..f64e197b9ef 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -1,16 +1,13 @@ diff --git a/libs/components/src/callout/callout.component.spec.ts b/libs/components/src/callout/callout.component.spec.ts index 36abae437d5..b7388da3492 100644 --- a/libs/components/src/callout/callout.component.spec.ts +++ b/libs/components/src/callout/callout.component.spec.ts @@ -12,7 +12,7 @@ describe("Callout", () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [CalloutComponent], + imports: [CalloutComponent], providers: [ { provide: I18nService, diff --git a/libs/components/src/callout/callout.component.ts b/libs/components/src/callout/callout.component.ts index bfeae17b359..6ffd8d2d0ec 100644 --- a/libs/components/src/callout/callout.component.ts +++ b/libs/components/src/callout/callout.component.ts @@ -1,7 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SharedModule } from "../shared"; +import { TypographyModule } from "../typography"; + export type CalloutTypes = "success" | "info" | "warning" | "danger"; const defaultIcon: Record = { @@ -22,6 +27,8 @@ let nextId = 0; @Component({ selector: "bit-callout", templateUrl: "callout.component.html", + standalone: true, + imports: [SharedModule, TypographyModule], }) export class CalloutComponent implements OnInit { @Input() type: CalloutTypes = "info"; @@ -42,13 +49,13 @@ export class CalloutComponent implements OnInit { get calloutClass() { switch (this.type) { case "danger": - return "tw-border-l-danger-600"; + return "tw-border-danger-600"; case "info": - return "tw-border-l-info-600"; + return "tw-border-info-600"; case "success": - return "tw-border-l-success-600"; + return "tw-border-success-600"; case "warning": - return "tw-border-l-warning-600"; + return "tw-border-warning-600"; } } diff --git a/libs/components/src/callout/callout.mdx b/libs/components/src/callout/callout.mdx index a40a970f895..3fdb53943b4 100644 --- a/libs/components/src/callout/callout.mdx +++ b/libs/components/src/callout/callout.mdx @@ -2,6 +2,10 @@ import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./callout.stories"; +```ts +import { CalloutModule } from "@bitwarden/components"; +``` + # Callouts diff --git a/libs/components/src/callout/callout.module.ts b/libs/components/src/callout/callout.module.ts index fac89dfbcf9..ad7e083fec7 100644 --- a/libs/components/src/callout/callout.module.ts +++ b/libs/components/src/callout/callout.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { CalloutComponent } from "./callout.component"; @NgModule({ - imports: [CommonModule], + imports: [CalloutComponent], exports: [CalloutComponent], - declarations: [CalloutComponent], }) export class CalloutModule {} diff --git a/libs/components/src/card/card.component.ts b/libs/components/src/card/card.component.ts index 3aaed26d8d0..37756088e0d 100644 --- a/libs/components/src/card/card.component.ts +++ b/libs/components/src/card/card.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectionStrategy, Component } from "@angular/core"; changeDetection: ChangeDetectionStrategy.OnPush, host: { class: - "tw-box-border tw-block tw-bg-background tw-text-main tw-border-solid tw-border-b tw-border-0 tw-border-b-secondary-300 [&:not(bit-layout_*)]:tw-rounded-lg tw-py-4 tw-px-3", + "tw-box-border tw-block tw-bg-background tw-text-main tw-border-solid tw-border-b tw-border-0 tw-border-b-secondary-300 [&:not(bit-layout_*)]:tw-rounded-lg [&:not(bit-layout_*)]:tw-border-b-shadow tw-py-4 bit-compact:tw-py-3 tw-px-3 bit-compact:tw-px-2", }, }) export class CardComponent {} diff --git a/libs/components/src/checkbox/checkbox.component.ts b/libs/components/src/checkbox/checkbox.component.ts index d8fd3f76eaa..0ce6f1889b5 100644 --- a/libs/components/src/checkbox/checkbox.component.ts +++ b/libs/components/src/checkbox/checkbox.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, HostBinding, Input, Optional, Self } from "@angular/core"; import { NgControl, Validators } from "@angular/forms"; @@ -7,6 +9,7 @@ import { BitFormControlAbstraction } from "../form-control"; selector: "input[type=checkbox][bitCheckbox]", template: "", providers: [{ provide: BitFormControlAbstraction, useExisting: CheckboxComponent }], + standalone: true, }) export class CheckboxComponent implements BitFormControlAbstraction { @HostBinding("class") @@ -17,14 +20,14 @@ export class CheckboxComponent implements BitFormControlAbstraction { "tw-transition", "tw-cursor-pointer", "tw-inline-block", + "tw-align-sub", "tw-rounded", "tw-border", "tw-border-solid", - "tw-border-secondary-600", - "tw-h-3.5", - "tw-w-3.5", + "tw-border-secondary-500", + "tw-h-[1.12rem]", + "tw-w-[1.12rem]", "tw-mr-1.5", - "tw-bottom-[-1px]", // Fix checkbox looking off-center "tw-flex-none", // Flexbox fix for bit-form-control "before:tw-content-['']", @@ -35,13 +38,16 @@ export class CheckboxComponent implements BitFormControlAbstraction { "hover:tw-border-2", "[&>label]:tw-border-2", - "focus-visible:tw-ring-2", - "focus-visible:tw-ring-offset-2", - "focus-visible:tw-ring-primary-700", + // if it exists, the parent form control handles focus + "[&:not(bit-form-control_*)]:focus-visible:tw-ring-2", + "[&:not(bit-form-control_*)]:focus-visible:tw-ring-offset-2", + "[&:not(bit-form-control_*)]:focus-visible:tw-ring-primary-600", "disabled:tw-cursor-auto", "disabled:tw-border", + "disabled:hover:tw-border", "disabled:tw-bg-secondary-100", + "disabled:hover:tw-bg-secondary-100", "checked:tw-bg-primary-600", "checked:tw-border-primary-600", @@ -53,6 +59,7 @@ export class CheckboxComponent implements BitFormControlAbstraction { "checked:before:tw-mask-position-[center]", "checked:before:tw-mask-repeat-[no-repeat]", "checked:disabled:tw-border-secondary-100", + "checked:disabled:hover:tw-border-secondary-100", "checked:disabled:tw-bg-secondary-100", "checked:disabled:before:tw-bg-text-muted", @@ -78,11 +85,11 @@ export class CheckboxComponent implements BitFormControlAbstraction { @HostBinding("style.--mask-image") protected maskImage = - `url('data:image/svg+xml,%3Csvg class="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="8" viewBox="0 0 10 10"%3E%3Cpath d="M0.5 6.2L2.9 8.6L9.5 1.4" fill="none" stroke="white" stroke-width="2"%3E%3C/path%3E%3C/svg%3E')`; + `url('data:image/svg+xml,%3Csvg class="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10" viewBox="0 0 10 10"%3E%3Cpath d="M0.5 6.2L2.9 8.6L9.5 1.4" fill="none" stroke="white" stroke-width="2"%3E%3C/path%3E%3C/svg%3E')`; @HostBinding("style.--indeterminate-mask-image") protected indeterminateImage = - `url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 13 13"%3E%3Cpath stroke="%23fff" stroke-width="2" d="M2.5 6.5h8"/%3E%3C/svg%3E%0A')`; + `url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 13 13"%3E%3Cpath stroke="%23fff" stroke-width="2" d="M2.5 6.5h8"/%3E%3C/svg%3E%0A')`; @HostBinding() @Input() diff --git a/libs/components/src/checkbox/checkbox.module.ts b/libs/components/src/checkbox/checkbox.module.ts index d03b9cf5050..3abfb4b1bfd 100644 --- a/libs/components/src/checkbox/checkbox.module.ts +++ b/libs/components/src/checkbox/checkbox.module.ts @@ -1,14 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormControlModule } from "../form-control"; -import { SharedModule } from "../shared"; - import { CheckboxComponent } from "./checkbox.component"; @NgModule({ - imports: [SharedModule, CommonModule, FormControlModule], - declarations: [CheckboxComponent], + imports: [CheckboxComponent], exports: [CheckboxComponent], }) export class CheckboxModule {} diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index 0d649eb433d..f3e5a5cb3f7 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -9,14 +9,18 @@ import { } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; +import { BadgeModule } from "../badge"; import { FormControlModule } from "../form-control"; +import { TableModule } from "../table"; import { I18nMockService } from "../utils/i18n-mock.service"; import { CheckboxModule } from "./checkbox.module"; -const template = ` +const template = /*html*/ `
@@ -54,7 +58,14 @@ export default { decorators: [ moduleMetadata({ declarations: [ExampleComponent], - imports: [FormsModule, ReactiveFormsModule, FormControlModule, CheckboxModule], + imports: [ + FormsModule, + ReactiveFormsModule, + FormControlModule, + CheckboxModule, + TableModule, + BadgeModule, + ], providers: [ { provide: I18nService, @@ -82,7 +93,10 @@ type Story = StoryObj; export const Default: Story = { render: (args) => ({ props: args, - template: ``, + template: /*html*/ ` + + + `, }), parameters: { docs: { @@ -91,9 +105,39 @@ export const Default: Story = { }, }, }, - args: { - checked: false, - disabled: false, +}; + +export const LongLabel: Story = { + render: () => ({ + props: { + formObj: new FormGroup({ + checkbox: new FormControl(false), + }), + }, + template: /*html*/ ` + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur iaculis consequat enim vitae elementum. + Ut non odio est. + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur iaculis consequat enim vitae elementum. + Ut non odio est. + Premium + + + + `, + }), + parameters: { + docs: { + source: { + code: template, + }, + }, }, }; @@ -104,7 +148,7 @@ export const Hint: Story = { checkbox: new FormControl(false), }), }, - template: ` + template: /*html*/ `
@@ -131,20 +175,37 @@ export const Hint: Story = { }, }; +export const Disabled: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + `, + }), + parameters: { + docs: { + source: { + code: template, + }, + }, + }, +}; + export const Custom: Story = { render: (args) => ({ props: args, - template: ` + template: /*html*/ `
-
-
+
diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index ec1c0690b19..f6cc3f133ad 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -1,15 +1,38 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { + AfterContentChecked, + ChangeDetectionStrategy, + Component, + ElementRef, + signal, + ViewChild, +} from "@angular/core"; + +import { TypographyModule } from "../typography"; @Component({ selector: "bit-item-content, [bit-item-content]", standalone: true, - imports: [CommonModule], + imports: [CommonModule, TypographyModule], templateUrl: `item-content.component.html`, host: { class: - "fvw-target tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + /** + * y-axis padding should be kept in sync with `item-action.component.ts`'s `top` and `bottom` units. + * we want this to be the same height as the `item-action`'s `:after` element + */ + "fvw-target tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", }, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ItemContentComponent {} +export class ItemContentComponent implements AfterContentChecked { + @ViewChild("endSlot") endSlot: ElementRef; + + protected endSlotHasChildren = signal(false); + + ngAfterContentChecked(): void { + this.endSlotHasChildren.set(this.endSlot?.nativeElement.childElementCount > 0); + } +} diff --git a/libs/components/src/item/item.component.html b/libs/components/src/item/item.component.html index 55e69006c57..812d6b0315b 100644 --- a/libs/components/src/item/item.component.html +++ b/libs/components/src/item/item.component.html @@ -1,21 +1,11 @@ - + + + +
- - - - -
- -
+
diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 58545a49b50..97a80484373 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -1,24 +1,16 @@ import { CommonModule } from "@angular/common"; -import { ChangeDetectionStrategy, Component, HostListener, signal } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + HostBinding, + HostListener, + signal, +} from "@angular/core"; import { A11yRowDirective } from "../a11y/a11y-row.directive"; import { ItemActionComponent } from "./item-action.component"; -/** - * The class used to set the height of a bit item's inner content. - */ -export const BitItemHeightClass = `tw-h-[52px]`; - -/** - * The height of a bit item in pixels. Includes any margin, padding, or border. Used by the virtual scroll - * to estimate how many items can be displayed at once and how large the virtual container should be. - * Needs to be updated if the item height or spacing changes. - * - * 52px + 5.25px bottom margin + 1px border = 58.25px - */ -export const BitItemHeight = 58.25; // - @Component({ selector: "bit-item", standalone: true, @@ -26,6 +18,10 @@ export const BitItemHeight = 58.25; // changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "item.component.html", providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], + host: { + class: + "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-cursor-pointer [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", + }, }) export class ItemComponent extends A11yRowDirective { /** @@ -40,4 +36,14 @@ export class ItemComponent extends A11yRowDirective { onFocusOut() { this.focusVisibleWithin.set(false); } + + @HostBinding("class") get classList(): string[] { + return [ + this.focusVisibleWithin() + ? "tw-z-10 tw-rounded tw-outline-none tw-ring-2 bit-compact:tw-ring-inset tw-ring-primary-600 tw-border-transparent".split( + " ", + ) + : "tw-border-b-shadow", + ].flat(); + } } diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index a08fcc70509..5adf9d3c49d 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -63,20 +63,20 @@ export const Default: Story = { template: /*html*/ ` - + - + - + @@ -135,16 +135,16 @@ export const TextOverflow: Story = { template: /*html*/ ` - + Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! Worlddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd! - + - + @@ -152,127 +152,129 @@ export const TextOverflow: Story = { }), }; -export const MultipleActionList: Story = { - render: (args) => ({ - props: args, - template: /*html*/ ` - - - +const multipleActionListTemplate = /*html*/ ` + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - `, + + + + + + + + + + + + + +`; + +export const MultipleActionList: Story = { + render: (args) => ({ + props: args, + template: multipleActionListTemplate, }), }; @@ -330,14 +332,14 @@ export const SingleActionWithBadge: Story = { Foobar - Auto-fill + Fill Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! - Auto-fill + Fill @@ -346,29 +348,40 @@ export const SingleActionWithBadge: Story = { }), }; +export const CompactMode: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` +
+ ${multipleActionListTemplate} +
+ `, + }), +}; + export const VirtualScrolling: Story = { render: (_args) => ({ props: { data: Array.from(Array(100000).keys()), }, template: /*html*/ ` - + - + - + - + @@ -392,7 +405,7 @@ export const WithoutBorderRadius: Story = { - + diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index 56c0af8f0db..ccbb40c2b57 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -13,7 +13,7 @@ >
-
+
= { primary: [ "!tw-text-primary-600", - "hover:!tw-text-primary-600", - "focus-visible:before:tw-ring-primary-700", - "disabled:!tw-text-primary-600/60", - ], - secondary: [ - "!tw-text-main", - "hover:!tw-text-main", - "focus-visible:before:tw-ring-primary-700", - "disabled:!tw-text-muted/60", + "hover:!tw-text-primary-700", + "focus-visible:before:tw-ring-primary-600", ], + secondary: ["!tw-text-main", "hover:!tw-text-main", "focus-visible:before:tw-ring-primary-600"], contrast: [ "!tw-text-contrast", "hover:!tw-text-contrast", "focus-visible:before:tw-ring-text-contrast", - "disabled:!tw-text-contrast/60", - ], - light: [ - "!tw-text-alt2", - "hover:!tw-text-alt2", - "focus-visible:before:tw-ring-text-alt2", - "disabled:!tw-text-alt2/60", ], + light: ["!tw-text-alt2", "hover:!tw-text-alt2", "focus-visible:before:tw-ring-text-alt2"], }; const commonStyles = [ "tw-text-unset", "tw-leading-none", - "tw-p-0", + "tw-px-0", + "tw-py-0.5", "tw-font-semibold", "tw-bg-transparent", "tw-border-0", "tw-border-none", "tw-rounded", "tw-transition", + "tw-no-underline", "hover:tw-underline", "hover:tw-decoration-1", "disabled:tw-no-underline", "disabled:tw-cursor-not-allowed", + "disabled:!tw-text-secondary-300", + "disabled:hover:!tw-text-secondary-300", + "disabled:hover:tw-no-underline", "focus-visible:tw-outline-none", "focus-visible:tw-underline", "focus-visible:tw-decoration-1", @@ -75,6 +68,7 @@ abstract class LinkDirective { @Directive({ selector: "a[bitLink]", + standalone: true, }) export class AnchorLinkDirective extends LinkDirective { @HostBinding("class") get classList() { @@ -86,6 +80,7 @@ export class AnchorLinkDirective extends LinkDirective { @Directive({ selector: "button[bitLink]", + standalone: true, }) export class ButtonLinkDirective extends LinkDirective { @HostBinding("class") get classList() { diff --git a/libs/components/src/link/link.mdx b/libs/components/src/link/link.mdx index 39d8de72fc4..e509ddb9911 100644 --- a/libs/components/src/link/link.mdx +++ b/libs/components/src/link/link.mdx @@ -10,23 +10,30 @@ import { LinkModule } from "@bitwarden/components"; # Link / Text button -Text Links and Buttons use the `primary-600` color and can use either the `` or ` @@ -60,7 +91,7 @@ export const Buttons: Story = { export const Anchors: StoryObj = { render: (args) => ({ props: args, - template: ` + template: /*html*/ `
Anchor @@ -91,7 +122,7 @@ export const Anchors: StoryObj = { export const Inline: Story = { render: (args) => ({ props: args, - template: ` + template: /*html*/ ` On the internet paragraphs often contain inline links, but few know that can be used for similar purposes. @@ -105,7 +136,7 @@ export const Inline: Story = { export const Disabled: Story = { render: (args) => ({ props: args, - template: ` + template: /*html*/ `
diff --git a/libs/components/src/menu/menu-divider.component.html b/libs/components/src/menu/menu-divider.component.html index 7d9fee3e8f2..98048261cff 100644 --- a/libs/components/src/menu/menu-divider.component.html +++ b/libs/components/src/menu/menu-divider.component.html @@ -1,5 +1,5 @@ diff --git a/libs/components/src/menu/menu-divider.component.ts b/libs/components/src/menu/menu-divider.component.ts index 194506ee50f..55b5c013c93 100644 --- a/libs/components/src/menu/menu-divider.component.ts +++ b/libs/components/src/menu/menu-divider.component.ts @@ -3,5 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "bit-menu-divider", templateUrl: "./menu-divider.component.html", + standalone: true, }) export class MenuDividerComponent {} diff --git a/libs/components/src/menu/menu-item.directive.ts b/libs/components/src/menu/menu-item.directive.ts index 3f4b23e1cc6..d0975e8e391 100644 --- a/libs/components/src/menu/menu-item.directive.ts +++ b/libs/components/src/menu/menu-item.directive.ts @@ -1,30 +1,33 @@ import { FocusableOption } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; @Component({ selector: "[bitMenuItem]", templateUrl: "menu-item.component.html", + standalone: true, + imports: [NgClass], }) export class MenuItemDirective implements FocusableOption { @HostBinding("class") classList = [ "tw-block", "tw-w-full", - "tw-py-1", - "tw-px-4", + "tw-py-1.5", + "tw-px-3", "!tw-text-main", "!tw-no-underline", "tw-cursor-pointer", "tw-border-none", "tw-bg-background", "tw-text-left", - "hover:tw-bg-secondary-100", - "focus-visible:tw-bg-secondary-100", + "hover:tw-bg-primary-100", "focus-visible:tw-z-50", "focus-visible:tw-outline-none", - "focus-visible:tw-ring", - "focus-visible:tw-ring-offset-2", - "focus-visible:tw-ring-primary-700", + "focus-visible:tw-ring-2", + "focus-visible:tw-rounded-lg", + "focus-visible:tw-ring-inset", + "focus-visible:tw-ring-primary-600", "active:!tw-ring-0", "active:!tw-ring-offset-0", "disabled:!tw-text-muted", @@ -39,7 +42,7 @@ export class MenuItemDirective implements FocusableOption { @Input({ transform: coerceBooleanProperty }) disabled?: boolean = false; - constructor(private elementRef: ElementRef) {} + constructor(public elementRef: ElementRef) {} focus() { this.elementRef.nativeElement.focus(); diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts index e49bb040d26..96d430c5e6a 100644 --- a/libs/components/src/menu/menu-trigger-for.directive.ts +++ b/libs/components/src/menu/menu-trigger-for.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Overlay, OverlayConfig, OverlayRef } from "@angular/cdk/overlay"; import { TemplatePortal } from "@angular/cdk/portal"; import { @@ -17,6 +19,7 @@ import { MenuComponent } from "./menu.component"; @Directive({ selector: "[bitMenuTriggerFor]", exportAs: "menuTrigger", + standalone: true, }) export class MenuTriggerForDirective implements OnDestroy { @HostBinding("attr.aria-expanded") isOpen = false; @@ -33,7 +36,7 @@ export class MenuTriggerForDirective implements OnDestroy { private defaultMenuConfig: OverlayConfig = { panelClass: "bit-menu-panel", hasBackdrop: true, - backdropClass: "cdk-overlay-transparent-backdrop", + backdropClass: ["cdk-overlay-transparent-backdrop", "bit-menu-panel-backdrop"], scrollStrategy: this.overlay.scrollStrategies.reposition(), positionStrategy: this.overlay .position() @@ -106,6 +109,7 @@ export class MenuTriggerForDirective implements OnDestroy { this.isOpen = false; this.disposeAll(); + this.menu.closed.emit(); } private getClosedEvents(): Observable { diff --git a/libs/components/src/menu/menu.component.html b/libs/components/src/menu/menu.component.html index 5b6b15b5cbe..31fd6df0a66 100644 --- a/libs/components/src/menu/menu.component.html +++ b/libs/components/src/menu/menu.component.html @@ -1,7 +1,7 @@
; @@ -31,7 +35,9 @@ export class MenuComponent implements AfterContentInit { ngAfterContentInit() { if (this.ariaRole === "menu") { - this.keyManager = new FocusKeyManager(this.menuItems).withWrap(); + this.keyManager = new FocusKeyManager(this.menuItems) + .withWrap() + .skipPredicate((item) => item.disabled); } } } diff --git a/libs/components/src/menu/menu.module.ts b/libs/components/src/menu/menu.module.ts index b165629e6c5..117460df559 100644 --- a/libs/components/src/menu/menu.module.ts +++ b/libs/components/src/menu/menu.module.ts @@ -1,6 +1,3 @@ -import { A11yModule } from "@angular/cdk/a11y"; -import { OverlayModule } from "@angular/cdk/overlay"; -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { MenuDividerComponent } from "./menu-divider.component"; @@ -9,8 +6,7 @@ import { MenuTriggerForDirective } from "./menu-trigger-for.directive"; import { MenuComponent } from "./menu.component"; @NgModule({ - imports: [A11yModule, CommonModule, OverlayModule], - declarations: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], + imports: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], exports: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], }) export class MenuModule {} diff --git a/libs/components/src/menu/menu.stories.ts b/libs/components/src/menu/menu.stories.ts index c5d232b2057..65fafd2d04d 100644 --- a/libs/components/src/menu/menu.stories.ts +++ b/libs/components/src/menu/menu.stories.ts @@ -3,23 +3,15 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { ButtonModule } from "../button/button.module"; -import { MenuDividerComponent } from "./menu-divider.component"; -import { MenuItemDirective } from "./menu-item.directive"; import { MenuTriggerForDirective } from "./menu-trigger-for.directive"; -import { MenuComponent } from "./menu.component"; +import { MenuModule } from "./menu.module"; export default { title: "Component Library/Menu", component: MenuTriggerForDirective, decorators: [ moduleMetadata({ - declarations: [ - MenuTriggerForDirective, - MenuComponent, - MenuItemDirective, - MenuDividerComponent, - ], - imports: [OverlayModule, ButtonModule], + imports: [MenuModule, OverlayModule, ButtonModule], }), ], parameters: { @@ -51,7 +43,7 @@ export const OpenMenu: Story = { Disabled button - +
@@ -67,7 +59,7 @@ export const ClosedMenu: Story = {
- + Anchor link Another link diff --git a/libs/components/src/multi-select/multi-select.component.html b/libs/components/src/multi-select/multi-select.component.html index 6e0db436157..0c45e2d333f 100644 --- a/libs/components/src/multi-select/multi-select.component.html +++ b/libs/components/src/multi-select/multi-select.component.html @@ -27,17 +27,15 @@ type="button" bitBadge variant="primary" - class="tw-mr-1 disabled:tw-border-0" + class="tw-mr-1 disabled:tw-border-0 tw-flex tw-gap-1.5 tw-items-center" [disabled]="disabled" (click)="clear(item)" > - - {{ item.labelName }} - + + + {{ item.labelName }} + + diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index b0a2cf613b6..53e51bfe2f9 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -1,4 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { hasModifierKey } from "@angular/cdk/keycodes"; +import { NgIf } from "@angular/common"; import { Component, Input, @@ -10,12 +14,20 @@ import { Optional, Self, } from "@angular/core"; -import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; -import { NgSelectComponent } from "@ng-select/ng-select"; +import { + ControlValueAccessor, + NgControl, + Validators, + ReactiveFormsModule, + FormsModule, +} from "@angular/forms"; +import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { BadgeModule } from "../badge"; import { BitFormFieldControl } from "../form-field/form-field-control"; +import { I18nPipe } from "../shared/i18n.pipe"; import { SelectItemView } from "./models/select-item-view"; @@ -26,6 +38,8 @@ let nextId = 0; selector: "bit-multi-select", templateUrl: "./multi-select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }], + standalone: true, + imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, NgIf, I18nPipe], }) /** * This component has been implemented to only support Multi-select list events @@ -39,7 +53,7 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro @Input() removeSelectedItems = false; @Input() placeholder: string; @Input() loading = false; - @Input() disabled = false; + @Input({ transform: coerceBooleanProperty }) disabled?: boolean; // Internal tracking of selected items protected selectedItems: SelectItemView[]; diff --git a/libs/components/src/multi-select/multi-select.module.ts b/libs/components/src/multi-select/multi-select.module.ts index 88de53b5481..c8cc899db00 100644 --- a/libs/components/src/multi-select/multi-select.module.ts +++ b/libs/components/src/multi-select/multi-select.module.ts @@ -1,16 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; -import { NgSelectModule } from "@ng-select/ng-select"; - -import { BadgeModule } from "../badge"; -import { SharedModule } from "../shared"; import { MultiSelectComponent } from "./multi-select.component"; @NgModule({ - imports: [CommonModule, FormsModule, NgSelectModule, BadgeModule, SharedModule], + imports: [MultiSelectComponent], exports: [MultiSelectComponent], - declarations: [MultiSelectComponent], }) export class MultiSelectModule {} diff --git a/libs/components/src/multi-select/scss/bw.theme.scss b/libs/components/src/multi-select/scss/bw.theme.scss index b567c725924..9e9ef275005 100644 --- a/libs/components/src/multi-select/scss/bw.theme.scss +++ b/libs/components/src/multi-select/scss/bw.theme.scss @@ -9,19 +9,18 @@ $ng-select-highlight: rgb(var(--color-primary-700)) !default; $ng-select-primary-text: rgb(var(--color-text-main)) !default; $ng-select-disabled-text: rgb(var(--color-secondary-100)) !default; $ng-select-border: rgb(var(--color-secondary-600)) !default; -$ng-select-border-radius: 4px !default; -$ng-select-bg: rgb(var(--color-background-alt)) !default; +$ng-select-border-radius: 0.5rem !default; +$ng-select-bg: rgb(var(--color-background)) !default; $ng-select-selected: transparent !default; -$ng-select-selected-alt: rgb(var(--color-text-main) / 0.06) !default; $ng-select-selected-text: $ng-select-primary-text !default; -$ng-select-marked: rgb(var(--color-text-main) / 0.12) !default; +$ng-select-marked: rgb(var(--color-primary-100)) !default; $ng-select-marked-text: $ng-select-primary-text !default; $ng-select-box-shadow: none !default; $ng-select-placeholder: rgb(var(--color-text-muted)) !default; -$ng-select-height: 35px !default; -$ng-select-value-padding-left: 10px !default; +$ng-select-height: 100%; +$ng-select-value-padding-left: 1rem !default; $ng-select-value-font-size: 0.9em !default; $ng-select-value-text: $ng-select-primary-text !default; @@ -31,7 +30,7 @@ $ng-select-dropdown-optgroup-text: rgb(var(--color-text-muted)) !default; $ng-select-dropdown-optgroup-marked: $ng-select-dropdown-optgroup-text !default; $ng-select-dropdown-option-bg: $ng-select-dropdown-bg !default; $ng-select-dropdown-option-text: $ng-select-primary-text !default; -$ng-select-dropdown-option-disabled: rgb(var(--color-text-muted) / 0.6) !default; +$ng-select-dropdown-option-disabled: rgb(var(--color-secondary-300)) !default; $ng-select-input-text: $ng-select-primary-text !default; @@ -41,12 +40,12 @@ $ng-clear-icon-hover: rgb(var(--color-text-main)) !default; $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; .ng-select { + height: $ng-select-height; &.ng-select-opened { > .ng-select-container { - background: $ng-select-bg; - border-color: $ng-select-border; + background: transparent; &:hover { - box-shadow: none; + box-shadow: $ng-select-box-shadow; } .ng-arrow { top: -2px; @@ -100,15 +99,15 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; color: $ng-select-primary-text; background-color: $ng-select-bg; border-radius: $ng-select-border-radius; - border: 1px solid $ng-select-border; - min-height: $ng-select-height; + border: none; + height: $ng-select-height; align-items: center; &:hover { - box-shadow: 0 1px 0 $ng-dropdown-shadow; + box-shadow: $ng-select-box-shadow; } .ng-value-container { align-items: center; - padding-left: $ng-select-value-padding-left; + padding: 6px 0px 5px $ng-select-value-padding-left; @include rtl { padding-right: $ng-select-value-padding-left; padding-left: 0; @@ -118,6 +117,7 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; } .ng-input { + padding-top: 2px; > input { color: $ng-select-input-text; } @@ -128,8 +128,9 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; .ng-select-container { height: $ng-select-height; .ng-value-container { + display: flex; + height: 100%; .ng-input { - top: 5px; left: 0; padding-left: $ng-select-value-padding-left; padding-right: 50px; @@ -153,19 +154,16 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; } .ng-select-container { .ng-value-container { - padding-top: 5px; - padding-left: 7px; + height: 100%; @include rtl { - padding-right: 7px; padding-left: 0; } .ng-value { font-size: $ng-select-value-font-size; - margin-bottom: 5px; color: $ng-select-value-text; background-color: $ng-select-selected; border-radius: 2px; - margin-right: 5px; + margin: 4px 5px 4px 0px; @include rtl { margin-right: 0; margin-left: 5px; @@ -206,21 +204,6 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; } } } - .ng-input { - padding: 0 0 3px 3px; - @include rtl { - padding: 0 3px 3px 0; - } - } - .ng-placeholder { - top: 5px; - padding-bottom: 5px; - padding-left: 3px; - @include rtl { - padding-right: 3px; - padding-left: 0; - } - } } } } @@ -230,6 +213,8 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; &:hover .ng-clear { color: $ng-clear-icon-hover; } + border-radius: $ng-select-border-radius; + text-align: center; } .ng-spinner-zone { padding: 5px 5px 0 0; @@ -262,7 +247,8 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; z-index: 2050 !important; background-color: $ng-select-dropdown-bg; border: 1px solid $ng-select-dropdown-border; - box-shadow: 0 1px 0 $ng-dropdown-shadow; + border-radius: $ng-select-border-radius; + box-shadow: $ng-select-box-shadow; left: 0; &.ng-select-top { bottom: 100%; @@ -335,6 +321,8 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; padding: 5px 7px; } .ng-dropdown-panel-items { + border-radius: $ng-select-border-radius; + background: $ng-select-bg; .ng-optgroup { user-select: none; padding: 8px 10px; @@ -357,10 +345,7 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default; .ng-option { background-color: $ng-select-dropdown-option-bg; color: $ng-select-dropdown-option-text; - padding: 8px 10px; - &.ng-option-selected { - background-color: $ng-select-selected-alt; - } + padding: 0.375rem 0.75rem; &.ng-option-selected.ng-option-marked { background-color: $ng-select-marked; } diff --git a/libs/components/src/navigation/nav-base.component.ts b/libs/components/src/navigation/nav-base.component.ts index 825a44deb0d..d641a984cd8 100644 --- a/libs/components/src/navigation/nav-base.component.ts +++ b/libs/components/src/navigation/nav-base.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; import { RouterLink, RouterLinkActive } from "@angular/router"; diff --git a/libs/components/src/navigation/nav-divider.component.ts b/libs/components/src/navigation/nav-divider.component.ts index 008d3f46c35..eff381e1c94 100644 --- a/libs/components/src/navigation/nav-divider.component.ts +++ b/libs/components/src/navigation/nav-divider.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { SideNavService } from "./side-nav.service"; @@ -5,6 +6,8 @@ import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-divider", templateUrl: "./nav-divider.component.html", + standalone: true, + imports: [CommonModule], }) export class NavDividerComponent { constructor(protected sideNavService: SideNavService) {} diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html index c22a067ffed..9f6d9ac034d 100644 --- a/libs/components/src/navigation/nav-group.component.html +++ b/libs/components/src/navigation/nav-group.component.html @@ -1,54 +1,56 @@ - - - - + + + + + - - - - - - - + + - - + + + + + + + - - -
- -
+ + +
+ +
+
diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 1ebe7338648..d615bfe0582 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -1,5 +1,7 @@ +import { CommonModule } from "@angular/common"; import { AfterContentInit, + booleanAttribute, Component, ContentChildren, EventEmitter, @@ -10,13 +12,22 @@ import { SkipSelf, } from "@angular/core"; +import { IconButtonModule } from "../icon-button"; +import { I18nPipe } from "../shared/i18n.pipe"; + import { NavBaseComponent } from "./nav-base.component"; +import { NavGroupAbstraction, NavItemComponent } from "./nav-item.component"; import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-group", templateUrl: "./nav-group.component.html", - providers: [{ provide: NavBaseComponent, useExisting: NavGroupComponent }], + providers: [ + { provide: NavBaseComponent, useExisting: NavGroupComponent }, + { provide: NavGroupAbstraction, useExisting: NavGroupComponent }, + ], + standalone: true, + imports: [CommonModule, NavItemComponent, IconButtonModule, I18nPipe], }) export class NavGroupComponent extends NavBaseComponent implements AfterContentInit { @ContentChildren(NavBaseComponent, { @@ -40,6 +51,12 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI @Input() open = false; + /** + * Automatically hide the nav group if there are no child buttons + */ + @Input({ transform: booleanAttribute }) + hideIfEmpty = false; + @Output() openChange = new EventEmitter(); @@ -53,6 +70,8 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI setOpen(isOpen: boolean) { this.open = isOpen; this.openChange.emit(this.open); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.open && this.parentNavGroup?.setOpen(this.open); } diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index 47a600727f4..a6fa53ff187 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -22,9 +22,7 @@ export default { title: "Component Library/Nav/Nav Group", component: NavGroupComponent, decorators: [ - positionFixedWrapperDecorator( - (story) => `${story}`, - ), + positionFixedWrapperDecorator((story) => `${story}`), moduleMetadata({ imports: [ SharedModule, @@ -73,17 +71,43 @@ export default { export const Default: StoryObj = { render: (args) => ({ props: args, - template: ` - - - - - - - - - - + template: /*html*/ ` + + + + + + + + + + + + + `, + }), +}; + +export const HideEmptyGroups: StoryObj = { + args: { + hideIfEmpty: true, + renderChildren: false, + }, + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + + + + + + + + `, }), }; @@ -91,22 +115,44 @@ export const Default: StoryObj = { export const Tree: StoryObj = { render: (args) => ({ props: args, - template: ` - - - - - - - - + template: /*html*/ ` + + + + + + + + + + + + - - + + + + `, + }), +}; + +export const Secondary: StoryObj = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + + + + + + - - + `, }), }; diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index 34fe0e63bc4..c3cf559389f 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -73,7 +73,7 @@
diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 521f31a53c3..9a811ce6777 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -1,9 +1,18 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, ElementRef, Input, ViewChild } from "@angular/core"; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + ReactiveFormsModule, + FormsModule, +} from "@angular/forms"; import { isBrowserSafariApi } from "@bitwarden/platform"; +import { InputModule } from "../input/input.module"; import { FocusableElement } from "../shared/focusable-element"; +import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; @@ -21,6 +30,8 @@ let nextId = 0; useExisting: SearchComponent, }, ], + standalone: true, + imports: [InputModule, ReactiveFormsModule, FormsModule, I18nPipe], }) export class SearchComponent implements ControlValueAccessor, FocusableElement { private notifyOnChange: (v: string) => void; @@ -35,6 +46,7 @@ export class SearchComponent implements ControlValueAccessor, FocusableElement { @Input() disabled: boolean; @Input() placeholder: string; + @Input() autocomplete: string; getFocusTarget() { return this.input.nativeElement; diff --git a/libs/components/src/search/search.module.ts b/libs/components/src/search/search.module.ts index 62072774900..cb9761eae6b 100644 --- a/libs/components/src/search/search.module.ts +++ b/libs/components/src/search/search.module.ts @@ -1,14 +1,9 @@ import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; - -import { InputModule } from "../input/input.module"; -import { SharedModule } from "../shared"; import { SearchComponent } from "./search.component"; @NgModule({ - imports: [SharedModule, InputModule, FormsModule], - declarations: [SearchComponent], + imports: [SearchComponent], exports: [SearchComponent], }) export class SearchModule {} diff --git a/libs/components/src/search/search.stories.ts b/libs/components/src/search/search.stories.ts index d4af6e676ae..a6cd714d43a 100644 --- a/libs/components/src/search/search.stories.ts +++ b/libs/components/src/search/search.stories.ts @@ -32,7 +32,7 @@ export default { type Story = StoryObj; export const Default: Story = { - render: (args: SearchComponent) => ({ + render: (args) => ({ props: args, template: ` diff --git a/libs/components/src/section/section-header.component.html b/libs/components/src/section/section-header.component.html index d17022d5eb7..f070cfeae02 100644 --- a/libs/components/src/section/section-header.component.html +++ b/libs/components/src/section/section-header.component.html @@ -1,8 +1,8 @@
-
+
-
+
diff --git a/libs/components/src/section/section.component.ts b/libs/components/src/section/section.component.ts index cd1b55b7c0f..ec34c804119 100644 --- a/libs/components/src/section/section.component.ts +++ b/libs/components/src/section/section.component.ts @@ -9,7 +9,8 @@ import { Component, Input } from "@angular/core"; template: `
diff --git a/libs/components/src/select/index.ts b/libs/components/src/select/index.ts index faebfee9bb8..8ef9993efda 100644 --- a/libs/components/src/select/index.ts +++ b/libs/components/src/select/index.ts @@ -1,3 +1,4 @@ export * from "./select.module"; export * from "./select.component"; +export * from "./option"; export * from "./option.component"; diff --git a/libs/components/src/select/option.component.ts b/libs/components/src/select/option.component.ts index d513feb7448..841ceda3648 100644 --- a/libs/components/src/select/option.component.ts +++ b/libs/components/src/select/option.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, booleanAttribute } from "@angular/core"; import { Option } from "./option"; @@ -5,6 +7,7 @@ import { Option } from "./option"; @Component({ selector: "bit-option", template: ``, + standalone: true, }) export class OptionComponent implements Option { @Input() diff --git a/libs/components/src/select/select.component.ts b/libs/components/src/select/select.component.ts index d189f1ab52c..8f75c5be42b 100644 --- a/libs/components/src/select/select.component.ts +++ b/libs/components/src/select/select.component.ts @@ -1,3 +1,6 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { NgIf } from "@angular/common"; import { Component, ContentChildren, @@ -10,8 +13,14 @@ import { Output, EventEmitter, } from "@angular/core"; -import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; -import { NgSelectComponent } from "@ng-select/ng-select"; +import { + ControlValueAccessor, + NgControl, + Validators, + ReactiveFormsModule, + FormsModule, +} from "@angular/forms"; +import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -26,6 +35,8 @@ let nextId = 0; selector: "bit-select", templateUrl: "select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: SelectComponent }], + standalone: true, + imports: [NgSelectModule, ReactiveFormsModule, FormsModule, NgIf], }) export class SelectComponent implements BitFormFieldControl, ControlValueAccessor { @ViewChild(NgSelectComponent) select: NgSelectComponent; @@ -60,7 +71,7 @@ export class SelectComponent implements BitFormFieldControl, ControlValueAcce this.selectedOption = this.findSelectedOption(this.items, this.selectedValue); } - @HostBinding("class") protected classes = ["tw-block", "tw-w-full"]; + @HostBinding("class") protected classes = ["tw-block", "tw-w-full", "tw-h-full"]; // Usings a separate getter for the HostBinding to get around an unexplained angular error @HostBinding("attr.disabled") diff --git a/libs/components/src/select/select.module.ts b/libs/components/src/select/select.module.ts index 4391a518174..8807ed63a48 100644 --- a/libs/components/src/select/select.module.ts +++ b/libs/components/src/select/select.module.ts @@ -1,14 +1,10 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; -import { NgSelectModule } from "@ng-select/ng-select"; import { OptionComponent } from "./option.component"; import { SelectComponent } from "./select.component"; @NgModule({ - imports: [CommonModule, NgSelectModule, FormsModule], - declarations: [SelectComponent, OptionComponent], + imports: [SelectComponent, OptionComponent], exports: [SelectComponent, OptionComponent], }) export class SelectModule {} diff --git a/libs/components/src/select/select.stories.ts b/libs/components/src/select/select.stories.ts index a90d534d340..4bc85d8dba2 100644 --- a/libs/components/src/select/select.stories.ts +++ b/libs/components/src/select/select.stories.ts @@ -2,6 +2,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FormFieldModule } from "../form-field"; import { MultiSelectComponent } from "../multi-select/multi-select.component"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -13,7 +14,7 @@ export default { component: SelectComponent, decorators: [ moduleMetadata({ - imports: [SelectModule], + imports: [SelectModule, FormFieldModule], providers: [ { provide: I18nService, @@ -44,12 +45,17 @@ export const Default: Story = { props: { ...args, }, - template: ` - - - - - `, + template: /*html*/ ` + + Choose a value + + + + + + + + `, }), args: {}, }; diff --git a/libs/components/src/shared/button-like.abstraction.ts b/libs/components/src/shared/button-like.abstraction.ts index e422bd4f7da..026a4b83024 100644 --- a/libs/components/src/shared/button-like.abstraction.ts +++ b/libs/components/src/shared/button-like.abstraction.ts @@ -1,7 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore export type ButtonType = "primary" | "secondary" | "danger" | "unstyled"; export abstract class ButtonLikeAbstraction { loading: boolean; disabled: boolean; - setButtonType: (value: ButtonType) => void; } diff --git a/libs/components/src/shared/compact-mode.service.ts b/libs/components/src/shared/compact-mode.service.ts new file mode 100644 index 00000000000..831add93676 --- /dev/null +++ b/libs/components/src/shared/compact-mode.service.ts @@ -0,0 +1,13 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Observable } from "rxjs"; + +/** Global config for the Bitwarden Design System */ +export abstract class CompactModeService { + /** + * When true, enables "compact mode". + * + * Component authors can also hook into compact mode with the `bit-compact:` Tailwind variant. + **/ + enabled$: Observable; +} diff --git a/libs/components/src/shared/focusable-element.ts b/libs/components/src/shared/focusable-element.ts index 1ea422aa6f2..7b063f4ddc9 100644 --- a/libs/components/src/shared/focusable-element.ts +++ b/libs/components/src/shared/focusable-element.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore /** * Interface for implementing focusable components. * diff --git a/libs/components/src/shared/i18n.pipe.ts b/libs/components/src/shared/i18n.pipe.ts index f428d9297c0..91bf0b3198d 100644 --- a/libs/components/src/shared/i18n.pipe.ts +++ b/libs/components/src/shared/i18n.pipe.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic */ @Pipe({ name: "i18n", + standalone: true, }) export class I18nPipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/components/src/shared/shared.module.ts b/libs/components/src/shared/shared.module.ts index dcf2e2bc05f..253b049f8fe 100644 --- a/libs/components/src/shared/shared.module.ts +++ b/libs/components/src/shared/shared.module.ts @@ -4,8 +4,7 @@ import { NgModule } from "@angular/core"; import { I18nPipe } from "./i18n.pipe"; @NgModule({ - imports: [CommonModule], - declarations: [I18nPipe], + imports: [CommonModule, I18nPipe], exports: [CommonModule, I18nPipe], }) export class SharedModule {} diff --git a/libs/components/src/stories/colors.mdx b/libs/components/src/stories/colors.mdx index f6b205be496..22079dfcbf7 100644 --- a/libs/components/src/stories/colors.mdx +++ b/libs/components/src/stories/colors.mdx @@ -25,6 +25,7 @@ export const Table = (args) => ( {Row("background-alt4")} + {Row("primary-100")} {Row("primary-300")} {Row("primary-600")} {Row("primary-700")} @@ -32,25 +33,38 @@ export const Table = (args) => ( {Row("secondary-100")} {Row("secondary-300")} + {Row("secondary-500")} {Row("secondary-600")} {Row("secondary-700")} + {Row("success-100")} {Row("success-600")} {Row("success-700")} + {Row("danger-100")} {Row("danger-600")} {Row("danger-700")} + {Row("warning-100")} {Row("warning-600")} {Row("warning-700")} + {Row("info-100")} {Row("info-600")} {Row("info-700")} + + {Row("notification-100")} + {Row("notification-600")} + + + {Row("art-primary")} + {Row("art-accent")} + Text diff --git a/libs/components/src/stories/compact-mode.mdx b/libs/components/src/stories/compact-mode.mdx new file mode 100644 index 00000000000..800bc34cf6a --- /dev/null +++ b/libs/components/src/stories/compact-mode.mdx @@ -0,0 +1,45 @@ +import { Meta, Story } from "@storybook/addon-docs"; + +import * as itemStories from "../item/item.stories"; +import * as popupLayoutStories from "../../../../apps/browser/src/platform/popup/layout/popup-layout.stories"; + + + +# Compact Mode + +The Bitwarden browser extension has a global compact mode setting for users who prefer a more +information-dense UI. + +## Tailwind + +Component authors can hook into this setting with the `bit-compact` Tailwind variant. In the +following example, the paragraph's padding is reduced when compact mode is enabled. + +```html +

Lorem impsum doggo dolor...

+ +
+

Lorem impsum doggo dolor...

+
+``` + +

Lorem impsum doggo dolor...

+ +
+

Lorem impsum doggo dolor...

+
+ +## Service + +To get/set compact mode in TypeScript, the `CompactModeService` exposes a `enabled$` observable. +However, styling with the Tailwind variant should be used when possible as it is more performant. + +## Examples + +### [Popup Layout](?path=/story/browser-popup-layout--compact-mode) + + + +### [Item](?path=/story/component-library-item--compact-mode) + + diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index a867d9cdf53..7709506f050 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -10,7 +10,7 @@ import { TableDataSource, TableModule } from "../../../table"; selector: "dialog-virtual-scroll-block", standalone: true, imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], - template: ` + template: /*html*/ ` diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index 4e8d884c4ac..c63b36ea89c 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -76,6 +76,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; linkType="primary" [bitPopoverTriggerFor]="myPopover" #triggerRef="popoverTrigger" + type="button" slot="end" > diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index ae4448eb76c..687b8917381 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -64,26 +64,24 @@ class KitchenSinkDialog {
-

About

+

About

-

Companies using Bitwarden

+

Companies using Bitwarden

-

Survey

+

Survey

diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index 203c510f814..a90597c1710 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -130,6 +130,9 @@ export const MenuOpen: Story = { const menuButton = getAllByRole(table, "button")[0]; await userEvent.click(menuButton); }, + parameters: { + chromatic: { ignoreSelectors: [".bit-menu-panel-backdrop"] }, + }, }; export const DefaultDialogOpen: Story = { @@ -193,4 +196,10 @@ export const VirtualScrollBlockingDialog: Story = { await userEvent.click(dialogButton); }, + parameters: { + chromatic: { + // TODO CL-524 fix flaky story (number of virtual scroll rows is inconsistent) + disableSnapshot: true, + }, + }, }; diff --git a/libs/components/src/styles.css b/libs/components/src/styles.css index ba200ff3f32..4f885115c87 100644 --- a/libs/components/src/styles.css +++ b/libs/components/src/styles.css @@ -7,5 +7,5 @@ @tailwind utilities; html { - font-size: 14px; + font-size: 16px; } diff --git a/libs/components/src/table/cell.directive.ts b/libs/components/src/table/cell.directive.ts index 61c75571063..8928fe7c095 100644 --- a/libs/components/src/table/cell.directive.ts +++ b/libs/components/src/table/cell.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostBinding } from "@angular/core"; @Directive({ selector: "th[bitCell], td[bitCell]", + standalone: true, }) export class CellDirective { @HostBinding("class") get classList() { diff --git a/libs/components/src/table/row.directive.ts b/libs/components/src/table/row.directive.ts index 19f3d3f775b..23347224af9 100644 --- a/libs/components/src/table/row.directive.ts +++ b/libs/components/src/table/row.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostBinding, Input } from "@angular/core"; @Directive({ selector: "tr[bitRow]", + standalone: true, }) export class RowDirective { @Input() alignContent: "top" | "middle" | "bottom" | "baseline" = "middle"; diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts index c6d60f155b2..d3309c03aa9 100644 --- a/libs/components/src/table/sortable.component.ts +++ b/libs/components/src/table/sortable.component.ts @@ -1,4 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass } from "@angular/common"; import { Component, HostBinding, Input, OnInit } from "@angular/core"; import type { SortDirection, SortFn } from "./table-data-source"; @@ -12,6 +15,8 @@ import { TableComponent } from "./table.component"; `, + standalone: true, + imports: [NgClass], }) export class SortableComponent implements OnInit { /** @@ -97,7 +102,6 @@ export class SortableComponent implements OnInit { get classList() { return [ - "tw-group", "tw-min-w-max", // Offset to border and padding diff --git a/libs/components/src/table/table-data-source.ts b/libs/components/src/table/table-data-source.ts index 8a5d994dc18..9a5d9ddf1f7 100644 --- a/libs/components/src/table/table-data-source.ts +++ b/libs/components/src/table/table-data-source.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { _isNumberValue } from "@angular/cdk/coercion"; import { DataSource } from "@angular/cdk/collections"; import { BehaviorSubject, combineLatest, map, Observable, Subscription } from "rxjs"; diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index 77647133bc0..34cd8c5d9ca 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -1,3 +1,12 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { + CdkVirtualScrollViewport, + CdkVirtualScrollableWindow, + CdkFixedSizeVirtualScroll, + CdkVirtualForOf, +} from "@angular/cdk/scrolling"; +import { CommonModule } from "@angular/common"; import { AfterContentChecked, Component, @@ -12,6 +21,7 @@ import { TrackByFunction, } from "@angular/core"; +import { RowDirective } from "./row.directive"; import { TableComponent } from "./table.component"; /** @@ -40,6 +50,15 @@ export class BitRowDef { selector: "bit-table-scroll", templateUrl: "./table-scroll.component.html", providers: [{ provide: TableComponent, useExisting: TableScrollComponent }], + standalone: true, + imports: [ + CommonModule, + CdkVirtualScrollViewport, + CdkVirtualScrollableWindow, + CdkFixedSizeVirtualScroll, + CdkVirtualForOf, + RowDirective, + ], }) export class TableScrollComponent extends TableComponent diff --git a/libs/components/src/table/table.component.ts b/libs/components/src/table/table.component.ts index 16f7a01b7c8..cd0a2a6c65e 100644 --- a/libs/components/src/table/table.component.ts +++ b/libs/components/src/table/table.component.ts @@ -1,4 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { isDataSource } from "@angular/cdk/collections"; +import { CommonModule } from "@angular/common"; import { AfterContentChecked, Component, @@ -14,6 +17,7 @@ import { TableDataSource } from "./table-data-source"; @Directive({ selector: "ng-template[body]", + standalone: true, }) export class TableBodyDirective { // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility @@ -23,6 +27,8 @@ export class TableBodyDirective { @Component({ selector: "bit-table", templateUrl: "./table.component.html", + standalone: true, + imports: [CommonModule], }) export class TableComponent implements OnDestroy, AfterContentChecked { @Input() dataSource: TableDataSource; diff --git a/libs/components/src/table/table.module.ts b/libs/components/src/table/table.module.ts index 1f1b705c69e..68993612772 100644 --- a/libs/components/src/table/table.module.ts +++ b/libs/components/src/table/table.module.ts @@ -9,8 +9,10 @@ import { BitRowDef, TableScrollComponent } from "./table-scroll.component"; import { TableBodyDirective, TableComponent } from "./table.component"; @NgModule({ - imports: [CommonModule, ScrollingModule, BitRowDef], - declarations: [ + imports: [ + CommonModule, + ScrollingModule, + BitRowDef, CellDirective, RowDirective, SortableComponent, diff --git a/libs/components/src/tabs/shared/tab-header.component.ts b/libs/components/src/tabs/shared/tab-header.component.ts index 4712df0549a..c45bafb3d52 100644 --- a/libs/components/src/tabs/shared/tab-header.component.ts +++ b/libs/components/src/tabs/shared/tab-header.component.ts @@ -10,5 +10,6 @@ import { Component } from "@angular/core"; "tw-h-16 tw-pl-4 tw-bg-background-alt tw-flex tw-items-end tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300", }, template: ``, + standalone: true, }) export class TabHeaderComponent {} diff --git a/libs/components/src/tabs/shared/tab-list-container.directive.ts b/libs/components/src/tabs/shared/tab-list-container.directive.ts index 1cf8a762d58..cedae44e582 100644 --- a/libs/components/src/tabs/shared/tab-list-container.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-container.directive.ts @@ -8,5 +8,6 @@ import { Directive } from "@angular/core"; host: { class: "tw-inline-flex tw-flex-wrap tw-leading-5", }, + standalone: true, }) export class TabListContainerDirective {} diff --git a/libs/components/src/tabs/shared/tab-list-item.directive.ts b/libs/components/src/tabs/shared/tab-list-item.directive.ts index 4b2030388f6..87435133a23 100644 --- a/libs/components/src/tabs/shared/tab-list-item.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-item.directive.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FocusableOption } from "@angular/cdk/a11y"; import { Directive, ElementRef, HostBinding, Input } from "@angular/core"; @@ -5,7 +7,10 @@ import { Directive, ElementRef, HostBinding, Input } from "@angular/core"; * Directive used for styling tab header items for both nav links (anchor tags) * and content tabs (button tags) */ -@Directive({ selector: "[bitTabListItem]" }) +@Directive({ + selector: "[bitTabListItem]", + standalone: true, +}) export class TabListItemDirective implements FocusableOption { @Input() active: boolean; @Input() disabled: boolean; diff --git a/libs/components/src/tabs/tab-group/tab-body.component.ts b/libs/components/src/tabs/tab-group/tab-body.component.ts index 18baa49ed06..45a6a05e7c2 100644 --- a/libs/components/src/tabs/tab-group/tab-body.component.ts +++ b/libs/components/src/tabs/tab-group/tab-body.component.ts @@ -1,9 +1,13 @@ -import { TemplatePortal } from "@angular/cdk/portal"; +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { TemplatePortal, CdkPortalOutlet } from "@angular/cdk/portal"; import { Component, HostBinding, Input } from "@angular/core"; @Component({ selector: "bit-tab-body", templateUrl: "tab-body.component.html", + standalone: true, + imports: [CdkPortalOutlet], }) export class TabBodyComponent { private _firstRender: boolean; diff --git a/libs/components/src/tabs/tab-group/tab-group.component.ts b/libs/components/src/tabs/tab-group/tab-group.component.ts index 59154d18caa..54d00343b38 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.ts +++ b/libs/components/src/tabs/tab-group/tab-group.component.ts @@ -1,5 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FocusKeyManager } from "@angular/cdk/a11y"; import { coerceNumberProperty } from "@angular/cdk/coercion"; +import { CommonModule } from "@angular/common"; import { AfterContentChecked, AfterContentInit, @@ -15,8 +18,11 @@ import { } from "@angular/core"; import { Subject, takeUntil } from "rxjs"; +import { TabHeaderComponent } from "../shared/tab-header.component"; +import { TabListContainerDirective } from "../shared/tab-list-container.directive"; import { TabListItemDirective } from "../shared/tab-list-item.directive"; +import { TabBodyComponent } from "./tab-body.component"; import { TabComponent } from "./tab.component"; /** Used to generate unique ID's for each tab component */ @@ -25,6 +31,14 @@ let nextId = 0; @Component({ selector: "bit-tab-group", templateUrl: "./tab-group.component.html", + standalone: true, + imports: [ + CommonModule, + TabHeaderComponent, + TabListContainerDirective, + TabListItemDirective, + TabBodyComponent, + ], }) export class TabGroupComponent implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy diff --git a/libs/components/src/tabs/tab-group/tab-label.directive.ts b/libs/components/src/tabs/tab-group/tab-label.directive.ts index 45da163631b..9a0e59845a1 100644 --- a/libs/components/src/tabs/tab-group/tab-label.directive.ts +++ b/libs/components/src/tabs/tab-group/tab-label.directive.ts @@ -16,6 +16,7 @@ import { Directive, TemplateRef } from "@angular/core"; */ @Directive({ selector: "[bitTabLabel]", + standalone: true, }) export class TabLabelDirective { constructor(public templateRef: TemplateRef) {} diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index 9a3a9380031..b2c9b1999bc 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TemplatePortal } from "@angular/cdk/portal"; import { Component, @@ -17,6 +19,7 @@ import { TabLabelDirective } from "./tab-label.directive"; host: { role: "tabpanel", }, + standalone: true, }) export class TabComponent implements OnInit { @Input() disabled = false; diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts index cec2bf947b6..0dac6681475 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts @@ -1,6 +1,8 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FocusableOption } from "@angular/cdk/a11y"; import { AfterViewInit, Component, HostListener, Input, OnDestroy, ViewChild } from "@angular/core"; -import { IsActiveMatchOptions, RouterLinkActive } from "@angular/router"; +import { IsActiveMatchOptions, RouterLinkActive, RouterModule } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; import { TabListItemDirective } from "../shared/tab-list-item.directive"; @@ -10,6 +12,8 @@ import { TabNavBarComponent } from "./tab-nav-bar.component"; @Component({ selector: "bit-tab-link", templateUrl: "tab-link.component.html", + standalone: true, + imports: [TabListItemDirective, RouterModule], }) export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts index e782a446c4c..305196a0c69 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FocusKeyManager } from "@angular/cdk/a11y"; import { AfterContentInit, @@ -8,6 +10,9 @@ import { QueryList, } from "@angular/core"; +import { TabHeaderComponent } from "../shared/tab-header.component"; +import { TabListContainerDirective } from "../shared/tab-list-container.directive"; + import { TabLinkComponent } from "./tab-link.component"; @Component({ @@ -16,6 +21,8 @@ import { TabLinkComponent } from "./tab-link.component"; host: { class: "tw-block", }, + standalone: true, + imports: [TabHeaderComponent, TabListContainerDirective], }) export class TabNavBarComponent implements AfterContentInit { @ContentChildren(forwardRef(() => TabLinkComponent)) tabLabels: QueryList; diff --git a/libs/components/src/tabs/tabs.module.ts b/libs/components/src/tabs/tabs.module.ts index fee1a8a7d08..ef1537db67e 100644 --- a/libs/components/src/tabs/tabs.module.ts +++ b/libs/components/src/tabs/tabs.module.ts @@ -1,11 +1,6 @@ -import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { RouterModule } from "@angular/router"; -import { TabHeaderComponent } from "./shared/tab-header.component"; -import { TabListContainerDirective } from "./shared/tab-list-container.directive"; -import { TabListItemDirective } from "./shared/tab-list-item.directive"; import { TabBodyComponent } from "./tab-group/tab-body.component"; import { TabGroupComponent } from "./tab-group/tab-group.component"; import { TabLabelDirective } from "./tab-group/tab-label.directive"; @@ -14,24 +9,21 @@ import { TabLinkComponent } from "./tab-nav-bar/tab-link.component"; import { TabNavBarComponent } from "./tab-nav-bar/tab-nav-bar.component"; @NgModule({ - imports: [CommonModule, RouterModule, PortalModule], - exports: [ + imports: [ + CommonModule, TabGroupComponent, TabComponent, TabLabelDirective, TabNavBarComponent, TabLinkComponent, + TabBodyComponent, ], - declarations: [ + exports: [ TabGroupComponent, TabComponent, TabLabelDirective, - TabListContainerDirective, - TabListItemDirective, - TabHeaderComponent, TabNavBarComponent, TabLinkComponent, - TabBodyComponent, ], }) export class TabsModule {} diff --git a/libs/components/src/toast/index.ts b/libs/components/src/toast/index.ts index f0b55402194..5afed111c0f 100644 --- a/libs/components/src/toast/index.ts +++ b/libs/components/src/toast/index.ts @@ -1,2 +1,3 @@ export * from "./toast.module"; export * from "./toast.service"; +export type { ToastVariant } from "./toast.component"; diff --git a/libs/components/src/toast/toast.component.html b/libs/components/src/toast/toast.component.html index f301995d0a6..84154cba611 100644 --- a/libs/components/src/toast/toast.component.html +++ b/libs/components/src/toast/toast.component.html @@ -1,5 +1,5 @@
@@ -8,17 +8,23 @@
{{ variant | i18n }}

{{ title }}

-

+

{{ m }}

+
-
+
diff --git a/libs/components/src/toast/toast.component.ts b/libs/components/src/toast/toast.component.ts index 4a31e005868..14fd5a7c62c 100644 --- a/libs/components/src/toast/toast.component.ts +++ b/libs/components/src/toast/toast.component.ts @@ -1,26 +1,29 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { IconButtonModule } from "../icon-button"; import { SharedModule } from "../shared"; +import { TypographyModule } from "../typography"; export type ToastVariant = "success" | "error" | "info" | "warning"; const variants: Record = { success: { - icon: "bwi-check", - bgColor: "tw-bg-success-600", + icon: "bwi-check-circle", + bgColor: "tw-bg-success-100", }, error: { icon: "bwi-error", - bgColor: "tw-bg-danger-600", + bgColor: "tw-bg-danger-100", }, info: { icon: "bwi-info-circle", - bgColor: "tw-bg-info-600", + bgColor: "tw-bg-info-100", }, warning: { icon: "bwi-exclamation-triangle", - bgColor: "tw-bg-warning-600", + bgColor: "tw-bg-warning-100", }, }; @@ -28,7 +31,7 @@ const variants: Record = { selector: "bit-toast", templateUrl: "toast.component.html", standalone: true, - imports: [SharedModule, IconButtonModule], + imports: [SharedModule, IconButtonModule, TypographyModule], }) export class ToastComponent { @Input() variant: ToastVariant = "info"; diff --git a/libs/components/src/toast/toast.module.ts b/libs/components/src/toast/toast.module.ts index bf39a0be9ad..bf17fde223f 100644 --- a/libs/components/src/toast/toast.module.ts +++ b/libs/components/src/toast/toast.module.ts @@ -1,13 +1,10 @@ -import { CommonModule } from "@angular/common"; import { ModuleWithProviders, NgModule } from "@angular/core"; import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr"; -import { ToastComponent } from "./toast.component"; import { BitwardenToastrComponent } from "./toastr.component"; @NgModule({ - imports: [CommonModule, ToastComponent], - declarations: [BitwardenToastrComponent], + imports: [BitwardenToastrComponent], exports: [BitwardenToastrComponent], }) export class ToastModule { diff --git a/libs/components/src/toast/toast.service.ts b/libs/components/src/toast/toast.service.ts index 974e7546e15..00c32f9b1b3 100644 --- a/libs/components/src/toast/toast.service.ts +++ b/libs/components/src/toast/toast.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { IndividualConfig, ToastrService } from "ngx-toastr"; diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index d209453d85c..382e19097b0 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -22,7 +22,7 @@ const toastServiceExampleTemplate = ` }) export class ToastServiceExampleComponent { @Input() - toastOptions: ToastOptions; + toastOptions?: ToastOptions; constructor(protected toastService: ToastService) {} } @@ -38,7 +38,7 @@ export default { }), applicationConfig({ providers: [ - ToastModule.forRoot().providers, + ToastModule.forRoot().providers!, { provide: I18nService, useFactory: () => { diff --git a/libs/components/src/toast/toast.tokens.css b/libs/components/src/toast/toast.tokens.css index 2ff9e99ae50..ad7bc2e379e 100644 --- a/libs/components/src/toast/toast.tokens.css +++ b/libs/components/src/toast/toast.tokens.css @@ -1,4 +1,5 @@ :root { --bit-toast-width: 19rem; --bit-toast-width-full: 96%; + --bit-toast-top: 4.3rem; } diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 70085dfc474..24209054948 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -2,13 +2,15 @@ import { animate, state, style, transition, trigger } from "@angular/animations" import { Component } from "@angular/core"; import { Toast as BaseToastrComponent } from "ngx-toastr"; +import { ToastComponent } from "./toast.component"; + @Component({ template: ` `, @@ -22,5 +24,7 @@ import { Toast as BaseToastrComponent } from "ngx-toastr"; ]), ], preserveWhitespaces: false, + standalone: true, + imports: [ToastComponent], }) export class BitwardenToastrComponent extends BaseToastrComponent {} diff --git a/libs/components/src/toast/toastr.css b/libs/components/src/toast/toastr.css index fabf8caf10c..3b569dc0b16 100644 --- a/libs/components/src/toast/toastr.css +++ b/libs/components/src/toast/toastr.css @@ -21,3 +21,7 @@ margin-left: auto; margin-right: auto; } + +.toast-container.toast-top-full-width { + top: var(--bit-toast-top); +} diff --git a/libs/components/src/toggle-group/toggle-group.component.spec.ts b/libs/components/src/toggle-group/toggle-group.component.spec.ts index 0fe863fcb9f..e418a7b410c 100644 --- a/libs/components/src/toggle-group/toggle-group.component.spec.ts +++ b/libs/components/src/toggle-group/toggle-group.component.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; diff --git a/libs/components/src/toggle-group/toggle-group.component.ts b/libs/components/src/toggle-group/toggle-group.component.ts index b59b90e894f..5033a27ed6d 100644 --- a/libs/components/src/toggle-group/toggle-group.component.ts +++ b/libs/components/src/toggle-group/toggle-group.component.ts @@ -13,6 +13,7 @@ let nextId = 0; selector: "bit-toggle-group", templateUrl: "./toggle-group.component.html", preserveWhitespaces: false, + standalone: true, }) export class ToggleGroupComponent { private id = nextId++; @@ -25,7 +26,7 @@ export class ToggleGroupComponent { @HostBinding("attr.role") role = "radiogroup"; @HostBinding("class") get classList() { - return ["tw-flex"].concat(this.fullWidth ? ["tw-w-full", "[&>*]:tw-grow"] : []); + return ["tw-flex"].concat(this.fullWidth ? ["tw-w-full", "[&>*]:tw-flex-1"] : []); } onInputInteraction(value: TValue) { diff --git a/libs/components/src/toggle-group/toggle-group.mdx b/libs/components/src/toggle-group/toggle-group.mdx index c5726555695..d553ae11298 100644 --- a/libs/components/src/toggle-group/toggle-group.mdx +++ b/libs/components/src/toggle-group/toggle-group.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs"; import * as stories from "./toggle-group.stories"; @@ -15,22 +15,36 @@ Toggle groups function as radio buttons and a radio group under the hood. A button in a toggle group can have a badge counter added to show the number of items existing within that filter. -For focus states, use `focus-visible`. +## Default + + - -## Accessibility +## Full width + +If a toggle group should span the width of its container, pass the `fullWidth` input to the toggle +group. + +``` + +...toggle components here... + +``` + + + +## Label wrap + +If the labels in a toggle group would overflow the width of the toggle group container, then the +labels will wrap to 2 lines and truncate with an ellipsis past that. The full label text is +accessible via the `title` prop (i.e. visible on hover). + + -- Follow contrast rules for the main button styles. -- Focus: - - Implement as a radio group with button styling and a context label (context label can be screen - reader only depending on use case). - - Since only 1 button can be selected at a time to filter the toggle group acts similarly to a - radio group. - - When moving focus to a button group, the focus should always move to the selected button. The - screen reader should then announce the button group: example “[context label], [button content] - selected, of [# of buttons]”), the number of buttons and the currently selected button. The user - may navigate the options then via left/right arrow keys. +# Accessibility -See WCAG for more: https://www.w3.org/WAI/ARIA/apg/patterns/radio/ +- Since only 1 button can be selected at a time, the toggle group acts similarly to a radio group. +- The user may navigate the options via left/right arrow keys. +- The screen reader will announce the button group: example “[context label], [button content] + selected, of [# of buttons]”), the number of buttons and the currently selected button. diff --git a/libs/components/src/toggle-group/toggle-group.module.ts b/libs/components/src/toggle-group/toggle-group.module.ts index fe1ce0ec52f..654149611f0 100644 --- a/libs/components/src/toggle-group/toggle-group.module.ts +++ b/libs/components/src/toggle-group/toggle-group.module.ts @@ -1,14 +1,10 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { BadgeModule } from "../badge"; - import { ToggleGroupComponent } from "./toggle-group.component"; import { ToggleComponent } from "./toggle.component"; @NgModule({ - imports: [CommonModule, BadgeModule], + imports: [ToggleGroupComponent, ToggleComponent], exports: [ToggleGroupComponent, ToggleComponent], - declarations: [ToggleGroupComponent, ToggleComponent], }) export class ToggleGroupModule {} diff --git a/libs/components/src/toggle-group/toggle-group.stories.ts b/libs/components/src/toggle-group/toggle-group.stories.ts index 8fdd22efe3f..fc8ea0ea929 100644 --- a/libs/components/src/toggle-group/toggle-group.stories.ts +++ b/libs/components/src/toggle-group/toggle-group.stories.ts @@ -13,8 +13,7 @@ export default { }, decorators: [ moduleMetadata({ - declarations: [ToggleGroupComponent, ToggleComponent], - imports: [BadgeModule], + imports: [BadgeModule, ToggleGroupComponent, ToggleComponent], }), ], parameters: { @@ -46,3 +45,74 @@ export const Default: Story = { selected: "all", }, }; + +export const FullWidth: Story = { + render: (args) => ({ + props: args, + template: /* HTML */ ` + + All 3 + + Invited + + Accepted 2 + + Deactivated + + `, + }), + args: { + selected: "all", + fullWidth: true, + }, +}; + +export const LabelWrap: Story = { + render: (args) => ({ + props: args, + template: /* HTML */ ` + fullWidth=false + + All + + Invited to a cool party with cool people + + + Accepted the invitation2 + + + Deactivatedinvitationswraplabel + +
+ fullWidth=true + + All + + Invited to a cool party with cool people + + + Accepted the invitation2 + + + Deactivatedinvitationswraplabel + + `, + }), + args: { + selected: "all", + fullWidth: true, + }, +}; diff --git a/libs/components/src/toggle-group/toggle.component.html b/libs/components/src/toggle-group/toggle.component.html index 471ed5f0c03..d036b1751ba 100644 --- a/libs/components/src/toggle-group/toggle.component.html +++ b/libs/components/src/toggle-group/toggle.component.html @@ -6,6 +6,11 @@ [checked]="selected" (change)="onInputInteraction()" /> -
diff --git a/libs/tools/send/send-ui/src/send-search/send-search.component.ts b/libs/tools/send/send-ui/src/send-search/send-search.component.ts index cd947490dd2..3d7d7d6285f 100644 --- a/libs/tools/send/send-ui/src/send-search/send-search.component.ts +++ b/libs/tools/send/send-ui/src/send-search/send-search.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; diff --git a/libs/tools/send/send-ui/src/services/send-list-filters.service.ts b/libs/tools/send/send-ui/src/services/send-list-filters.service.ts index b3a21a38332..b266ad08a69 100644 --- a/libs/tools/send/send-ui/src/services/send-list-filters.service.ts +++ b/libs/tools/send/send-ui/src/services/send-list-filters.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { map, Observable, startWith } from "rxjs"; diff --git a/libs/tools/send/send-ui/tsconfig.json b/libs/tools/send/send-ui/tsconfig.json index c52bfd7b0df..671154e0a04 100644 --- a/libs/tools/send/send-ui/tsconfig.json +++ b/libs/tools/send/send-ui/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../../../shared/tsconfig.libs", + "extends": "../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../../angular/src/*"], + "@bitwarden/auth/common": ["../../../auth/src/common"], + "@bitwarden/common/*": ["../../../common/src/*"], + "@bitwarden/components": ["../../../components/src"], + "@bitwarden/generator-components": ["../../../tools/generator/components/src"], + "@bitwarden/generator-core": ["../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../../../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../../../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../../key-management/src"], + "@bitwarden/platform": ["../../../platform/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index e960eed9f1a..e33c115e8dd 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts b/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts index d6d95565869..72bbb0da12c 100644 --- a/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts +++ b/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts @@ -6,4 +6,9 @@ export abstract class TotpCaptureService { * Captures a TOTP secret and returns it as a string. Returns null if no TOTP secret was found. */ abstract captureTotpSecret(): Promise; + /** + * Returns whether the TOTP secret can be captured from the current tab. + * Only available in the browser extension and when not in a popout window. + */ + abstract canCaptureTotp(window: Window): boolean; } diff --git a/libs/vault/src/cipher-form/cipher-form-container.ts b/libs/vault/src/cipher-form/cipher-form-container.ts index 8010cf260df..54f194598e5 100644 --- a/libs/vault/src/cipher-form/cipher-form-container.ts +++ b/libs/vault/src/cipher-form/cipher-form-container.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherFormConfig } from "@bitwarden/vault"; diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index e48cf384c2c..f7146776b73 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -31,7 +31,8 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests"; import { CipherFormService } from "./abstractions/cipher-form.service"; @@ -152,6 +153,7 @@ export default { provide: TotpCaptureService, useValue: { captureTotpSecret: () => Promise.resolve("some-value"), + canCaptureTotp: () => true, }, }, { @@ -232,7 +234,7 @@ export const Edit: Story = { config: { ...defaultConfig, mode: "edit", - originalCipher: defaultConfig.originalCipher, + originalCipher: defaultConfig.originalCipher!, }, }, }; @@ -243,7 +245,7 @@ export const PartialEdit: Story = { config: { ...defaultConfig, mode: "partial-edit", - originalCipher: defaultConfig.originalCipher, + originalCipher: defaultConfig.originalCipher!, }, }, }; @@ -254,7 +256,7 @@ export const Clone: Story = { config: { ...defaultConfig, mode: "clone", - originalCipher: defaultConfig.originalCipher, + originalCipher: defaultConfig.originalCipher!, }, }, }; @@ -267,7 +269,7 @@ export const NoPersonalOwnership: Story = { mode: "add", allowPersonalOwnership: false, originalCipher: defaultConfig.originalCipher, - organizations: defaultConfig.organizations, + organizations: defaultConfig.organizations!, }, }, }; diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html index 0b0c729de89..c00f51c8eba 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html @@ -6,7 +6,7 @@

{{ "additionalOptions" | i18n }}

{{ "notes" | i18n }} - + { expect(showToast).toHaveBeenCalledWith({ variant: "success", - title: null, + title: "", message: "deletedAttachment", }); }); diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index 932b2df2e17..b1ada907b1d 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -22,10 +22,10 @@ import { }) export class DeleteAttachmentComponent { /** Id of the cipher associated with the attachment */ - @Input({ required: true }) cipherId: string; + @Input({ required: true }) cipherId!: string; /** The attachment that is can be deleted */ - @Input({ required: true }) attachment: AttachmentView; + @Input({ required: true }) attachment!: AttachmentView; /** Emits when the attachment is successfully deleted */ @Output() onDeletionSuccess = new EventEmitter(); @@ -54,7 +54,7 @@ export class DeleteAttachmentComponent { this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("deletedAttachment"), }); diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts index 40f7eeecf07..cf73f3dbed4 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LiveAnnouncer } from "@angular/cdk/a11y"; import { AsyncPipe, NgForOf, NgIf } from "@angular/common"; import { Component, OnInit, QueryList, ViewChildren } from "@angular/core"; diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts index 4af80ed464c..f712e3114e0 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { NgForOf, NgIf } from "@angular/common"; import { Component, diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index bc4ff608805..63c5906b570 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index d1bbbef0910..3d3e04864e8 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { NgIf } from "@angular/common"; import { AfterViewInit, diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html index 445908679c3..16bccebb939 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html @@ -2,9 +2,11 @@ *ngIf="type === 'password'" [disableMargin]="disableMargin" (onGenerated)="onCredentialGenerated($event)" + (onAlgorithm)="algorithm($event)" > diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts index 79fac29d4d9..fdde5e15d91 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts @@ -1,9 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { GeneratorModule } from "@bitwarden/generator-components"; -import { GeneratedCredential } from "@bitwarden/generator-core"; +import { AlgorithmInfo, GeneratedCredential } from "@bitwarden/generator-core"; /** * Renders a password or username generator UI and emits the most recently generated value. @@ -16,6 +18,9 @@ import { GeneratedCredential } from "@bitwarden/generator-core"; imports: [CommonModule, GeneratorModule], }) export class CipherFormGeneratorComponent { + @Input() + algorithm: (selected: AlgorithmInfo) => void; + /** * The type of generator form to show. */ diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts index f63a974d487..62d171b8436 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts @@ -38,6 +38,8 @@ describe("AddEditCustomFieldDialogComponent", () => { fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent); component = fixture.componentInstance; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions fixture.detectChanges; }); diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts index 02ec709de5d..9593d6affff 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts index 0c0fa1b4184..a5e57d2858f 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts @@ -19,6 +19,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; import { DialogService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "../../../../../components/src/form-field/password-input-toggle.directive"; import { CipherFormConfig } from "../../abstractions/cipher-form-config.service"; import { CipherFormContainer } from "../../cipher-form-container"; diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts index 1aeb9e0da08..d723bad5751 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { LiveAnnouncer } from "@angular/cdk/a11y"; import { DialogRef } from "@angular/cdk/dialog"; import { CdkDragDrop, DragDropModule, moveItemInArray } from "@angular/cdk/drag-drop"; diff --git a/libs/vault/src/cipher-form/components/identity/identity.component.ts b/libs/vault/src/cipher-form/components/identity/identity.component.ts index d8f938f4ae7..b1403231ecf 100644 --- a/libs/vault/src/cipher-form/components/identity/identity.component.ts +++ b/libs/vault/src/cipher-form/components/identity/identity.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts index 93229bda6c3..32c1e7417e4 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts @@ -91,7 +91,8 @@ describe("ItemDetailsSectionComponent", () => { id: "col1", name: "Collection 1", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, ]; component.originalCipherView = { @@ -125,13 +126,15 @@ describe("ItemDetailsSectionComponent", () => { id: "col1", name: "Collection 1", organizationId: "org1", - canEditItems: (_org) => false, + assigned: true, + readOnly: true, } as CollectionView, { id: "col2", name: "Collection 2", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, ]; component.originalCipherView = { @@ -386,19 +389,22 @@ describe("ItemDetailsSectionComponent", () => { id: "col1", name: "Collection 1", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, { id: "col2", name: "Collection 2", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, { id: "col3", name: "Collection 3", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, ]; @@ -421,7 +427,8 @@ describe("ItemDetailsSectionComponent", () => { id: "col1", name: "Collection 1", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, ]; @@ -453,20 +460,22 @@ describe("ItemDetailsSectionComponent", () => { id: "col1", name: "Collection 1", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, { id: "col2", name: "Collection 2", organizationId: "org1", - canEditItems: (_org) => true, + assigned: true, + readOnly: false, } as CollectionView, { id: "col3", name: "Collection 3", organizationId: "org1", readOnly: true, - canEditItems: (_org) => true, + assigned: true, } as CollectionView, ]; @@ -490,21 +499,21 @@ describe("ItemDetailsSectionComponent", () => { name: "Collection 1", organizationId: "org1", readOnly: true, - canEditItems: (_org) => false, + assigned: false, } as CollectionView, { id: "col2", name: "Collection 2", organizationId: "org1", readOnly: true, - canEditItems: (_org) => false, + assigned: false, } as CollectionView, { id: "col3", name: "Collection 3", organizationId: "org1", readOnly: false, - canEditItems: (_org) => false, + assigned: true, } as CollectionView, ]; @@ -527,20 +536,20 @@ describe("ItemDetailsSectionComponent", () => { name: "Collection 1", organizationId: "org1", readOnly: true, - canEditItems: (_org) => false, + assigned: false, } as CollectionView, { id: "col2", name: "Collection 2", organizationId: "org1", - canEditItems: (_org) => false, + assigned: false, } as CollectionView, { id: "col3", name: "Collection 3", organizationId: "org1", readOnly: true, - canEditItems: (_org) => false, + assigned: false, } as CollectionView, ]; component.originalCipherView = { diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index ea82aa0cae4..f7fd228232e 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, NgClass } from "@angular/common"; import { Component, DestroyRef, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -271,19 +273,25 @@ export class ItemDetailsSectionComponent implements OnInit { this.showCollectionsControl = true; } - const organization = this.organizations.find((o) => o.id === orgId); - this.collectionOptions = this.collections .filter((c) => { - // Filter criteria: - // - The collection belongs to the organization - // - When in partial edit mode, show all org collections because the control is disabled. - // - The user can edit items within the collection - // - When viewing as an admin, all collections should be shown, even readonly. When non-admin, filter out readonly collections - return ( - c.organizationId === orgId && - (this.partialEdit || c.canEditItems(organization) || this.config.admin) - ); + // The collection belongs to the organization + if (c.organizationId !== orgId) { + return false; + } + + // When in partial edit mode, show all org collections because the control is disabled. + if (this.partialEdit) { + return true; + } + + // When viewing as an admin, all collections should be shown, even readonly. (AC Only) + if (this.config.admin) { + return true; + } + + // Non-admins can only select assigned collections that are not read only. (Non-AC) + return c.assigned && !c.readOnly; }) .map((c) => ({ id: c.id, diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts index 232a4b2d27b..69cfd419742 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts @@ -14,6 +14,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "@bitwarden/components/src/form-field/password-input-toggle.directive"; import { CipherFormGenerationService } from "../../abstractions/cipher-form-generation.service"; @@ -434,6 +436,7 @@ describe("LoginDetailsSectionComponent", () => { }); it("should call captureTotp when the capture totp button is clicked", fakeAsync(() => { + jest.spyOn(component, "canCaptureTotp", "get").mockReturnValue(true); component.captureTotp = jest.fn(); fixture.detectChanges(); @@ -445,7 +448,8 @@ describe("LoginDetailsSectionComponent", () => { })); describe("canCaptureTotp", () => { - it("should return true when totpCaptureService is present and totp is editable", () => { + it("should return true when totpCaptureService is present and totpCaptureService.canCaptureTotp is true and totp is editable", () => { + jest.spyOn(component, "canCaptureTotp", "get").mockReturnValue(true); component.loginDetailsForm.controls.totp.enable(); expect(component.canCaptureTotp).toBe(true); }); diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts index 691b05be2b4..2296932aca3 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe, NgIf } from "@angular/common"; import { Component, inject, OnInit, Optional } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -63,10 +65,14 @@ export class LoginDetailsSectionComponent implements OnInit { newPasswordGenerated: boolean; /** - * Whether the TOTP field can be captured from the current tab. Only available in the browser extension. + * Whether the TOTP field can be captured from the current tab. Only available in the browser extension and + * when not in a popout window. */ get canCaptureTotp() { - return this.totpCaptureService != null && this.loginDetailsForm.controls.totp.enabled; + return ( + !!this.totpCaptureService?.canCaptureTotp(window) && + this.loginDetailsForm.controls.totp.enabled + ); } private datePipe = inject(DatePipe); diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts index a15237421bd..134897c9356 100644 --- a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts +++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts @@ -1,10 +1,16 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { CardComponent, FormFieldModule, @@ -14,6 +20,7 @@ import { SelectModule, TypographyModule, } from "@bitwarden/components"; +import { generate_ssh_key } from "@bitwarden/sdk-internal"; import { CipherFormContainer } from "../../cipher-form-container"; @@ -48,20 +55,35 @@ export class SshKeySectionComponent implements OnInit { * leaving as just null gets inferred as `unknown` */ sshKeyForm = this.formBuilder.group({ - privateKey: null as string | null, - publicKey: null as string | null, - keyFingerprint: null as string | null, + privateKey: [""], + publicKey: [""], + keyFingerprint: [""], }); constructor( private cipherFormContainer: CipherFormContainer, private formBuilder: FormBuilder, private i18nService: I18nService, - ) {} + private sdkService: SdkService, + ) { + this.cipherFormContainer.registerChildForm("sshKeyDetails", this.sshKeyForm); + this.sshKeyForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { + const data = new SshKeyView(); + data.privateKey = value.privateKey; + data.publicKey = value.publicKey; + data.keyFingerprint = value.keyFingerprint; + this.cipherFormContainer.patchCipher((cipher) => { + cipher.sshKey = data; + return cipher; + }); + }); + } - ngOnInit() { - if (this.originalCipherView?.card) { + async ngOnInit() { + if (this.originalCipherView?.sshKey) { this.setInitialValues(); + } else { + await this.generateSshKey(); } this.sshKeyForm.disable(); @@ -77,4 +99,14 @@ export class SshKeySectionComponent implements OnInit { keyFingerprint, }); } + + private async generateSshKey() { + await firstValueFrom(this.sdkService.client$); + const sshKey = generate_ssh_key("Ed25519"); + this.sshKeyForm.setValue({ + privateKey: sshKey.private_key, + publicKey: sshKey.public_key, + keyFingerprint: sshKey.key_fingerprint, + }); + } } diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts index 6b607e3048b..93a53345d3a 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject, Injectable } from "@angular/core"; import { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs"; @@ -5,6 +7,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherId } 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"; @@ -29,12 +32,17 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { private cipherService: CipherService = inject(CipherService); private folderService: FolderService = inject(FolderService); private collectionService: CollectionService = inject(CollectionService); + private accountService = inject(AccountService); + + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); async buildConfig( mode: CipherFormMode, cipherId?: CipherId, cipherType?: CipherType, ): Promise { + const activeUserId = await firstValueFrom(this.activeUserId$); + const [organizations, collections, allowPersonalOwnership, folders, cipher] = await firstValueFrom( combineLatest([ @@ -47,9 +55,9 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { ), ), this.allowPersonalOwnership$, - this.folderService.folders$.pipe( + this.folderService.folders$(activeUserId).pipe( switchMap((f) => - this.folderService.folderViews$.pipe( + this.folderService.folderViews$(activeUserId).pipe( filter((d) => d.length - 1 === f.length), // -1 for "No Folder" in folderViews$ ), ), diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 1b7e86f82a7..92a28f9b15f 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { inject, Injectable } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; @@ -71,7 +73,6 @@ export class DefaultCipherFormService implements CipherFormService { await this.cipherService.updateWithServer( encryptedCipher, config.admin || originalCollectionIds.size === 0, - config.mode !== "clone", ); // Then save the new collection changes separately diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.html b/libs/vault/src/cipher-view/additional-options/additional-options.component.html index dc785827767..cc74d4e3a68 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.html +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.html @@ -4,8 +4,8 @@

{{ "additionalOptions" | i18n }}

- {{ "note" | i18n }} - + {{ "note" | i18n }} + - {{ "expiration" | i18n }} + {{ "expiration" | i18n }} {{ setSectionTitle }} /> - {{ "securityCode" | i18n }} + {{ "securityCode" | i18n }} a?.id)); /** * Optional list of collections the cipher is assigned to. If none are provided, they will be fetched using the * `CipherService` and the `collectionIds` property of the cipher. */ - @Input() collections: CollectionView[]; + @Input() collections?: CollectionView[]; /** Should be set to true when the component is used within the Admin Console */ @Input() isAdminConsole?: boolean = false; - organization$: Observable; - folder$: Observable; + organization$: Observable | undefined; + folder$: Observable | undefined; private destroyed$: Subject = new Subject(); cardIsExpired: boolean = false; @@ -66,6 +69,7 @@ export class CipherViewComponent implements OnChanges, OnDestroy { private organizationService: OrganizationService, private collectionService: CollectionService, private folderService: FolderService, + private accountService: AccountService, ) {} async ngOnChanges() { @@ -84,24 +88,38 @@ export class CipherViewComponent implements OnChanges, OnDestroy { } get hasCard() { + if (!this.cipher) { + return false; + } + const { cardholderName, code, expMonth, expYear, number } = this.cipher.card; return cardholderName || code || expMonth || expYear || number; } get hasLogin() { + if (!this.cipher) { + return false; + } + const { username, password, totp } = this.cipher.login; return username || password || totp; } get hasAutofill() { - return this.cipher.login?.uris.length > 0; + const uris = this.cipher?.login?.uris.length ?? 0; + + return uris > 0; } get hasSshKey() { - return this.cipher.sshKey?.privateKey; + return !!this.cipher?.sshKey?.privateKey; } async loadCipherData() { + if (!this.cipher) { + return; + } + // Load collections if not provided and the cipher has collectionIds if ( this.cipher.collectionIds && @@ -122,8 +140,14 @@ export class CipherViewComponent implements OnChanges, OnDestroy { } if (this.cipher.folderId) { + const activeUserId = await firstValueFrom(this.activeUserId$); + + if (!activeUserId) { + return; + } + this.folder$ = this.folderService - .getDecrypted$(this.cipher.folderId) + .getDecrypted$(this.cipher.folderId, activeUserId) .pipe(takeUntil(this.destroyed$)); } } diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index b82b2e7a8a3..45ddc3c1dea 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -10,7 +10,7 @@

{{ "customFields" | i18n }}

data-testid="custom-field" > - {{ field.name }} + {{ field.name }} - {{ field.name }} + {{ field.name }} {{ "customFields" | i18n }} aria-readonly="true" disabled /> - {{ field.name }} + + {{ field.name }} +
{{ "cfTypeLinked" | i18n }}: {{ field.name }} diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index 01f05765bcb..b41b351e197 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html index b6b4256440f..28bd0bd2581 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html @@ -7,11 +7,12 @@

{{ "itemDetails" | i18n }}

[disableMargin]="!cipher.collectionIds?.length && !showOwnership && !cipher.folderId" [disableReadOnlyBorder]="!cipher.collectionIds?.length && !showOwnership && !cipher.folderId" > - + {{ "itemName" | i18n }} {{ "itemHistory" | i18n }} {{ "datePasswordUpdated" | i18n }}: {{ cipher.passwordRevisionDisplayDate | date: "medium" }}

-
{{ "passwordHistory" | i18n }} - + diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts index 4a37c9a491b..f49d7030d77 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts @@ -1,9 +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, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CipherId } from "@bitwarden/common/types/guid"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -43,6 +44,6 @@ export class ItemHistoryV2Component { * View the password history for the cipher. */ async viewPasswordHistory() { - await this.viewPasswordHistoryService.viewPasswordHistory(this.cipher?.id as CipherId); + await this.viewPasswordHistoryService.viewPasswordHistory(this.cipher); } } diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index afc38a58d65..b4a0d4841f8 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -4,10 +4,11 @@

{{ "loginCredentials" | i18n }}

- + {{ "username" | i18n }} {{ "loginCredentials" | i18n }} > - {{ "password" | i18n }} + + {{ "password" | i18n }} + + + + + diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts new file mode 100644 index 00000000000..9bb96d1accd --- /dev/null +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts @@ -0,0 +1,218 @@ +import { DebugElement } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { CopyClickDirective } from "@bitwarden/angular/directives/copy-click.directive"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { EventType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; +import { BitFormFieldComponent, ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports +import { BitPasswordInputToggleDirective } from "@bitwarden/components/src/form-field/password-input-toggle.directive"; + +import { LoginCredentialsViewComponent } from "./login-credentials-view.component"; + +describe("LoginCredentialsViewComponent", () => { + let component: LoginCredentialsViewComponent; + let fixture: ComponentFixture; + + const hasPremiumFromAnySource$ = new BehaviorSubject(true); + const mockAccount = { + id: "test-user-id" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + type: 0, + status: 0, + kdf: 0, + kdfIterations: 0, + }; + const activeAccount$ = new BehaviorSubject(mockAccount); + + const cipher = { + id: "cipher-id", + name: "Mock Cipher", + type: CipherType.Login, + login: new LoginView(), + } as CipherView; + + cipher.login.password = "cipher-password"; + cipher.login.username = "cipher-username"; + const date = new Date("2024-02-02"); + cipher.login.fido2Credentials = [{ creationDate: date } as Fido2CredentialView]; + + const collect = jest.fn(); + + beforeEach(async () => { + collect.mockClear(); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: BillingAccountProfileStateService, + useValue: mock({ + hasPremiumFromAnySource$: () => hasPremiumFromAnySource$, + }), + }, + { provide: AccountService, useValue: mock({ activeAccount$ }) }, + { provide: PremiumUpgradePromptService, useValue: mock() }, + { provide: EventCollectionService, useValue: mock({ collect }) }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: ToastService, useValue: mock() }, + { provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(LoginCredentialsViewComponent); + component = fixture.componentInstance; + component.cipher = cipher; + fixture.detectChanges(); + }); + + describe("username", () => { + let usernameField: DebugElement; + + beforeEach(() => { + usernameField = fixture.debugElement.queryAll(By.directive(BitFormFieldComponent))[0]; + }); + + it("displays the username", () => { + const usernameInput = usernameField.query(By.css("input")).nativeElement; + + expect(usernameInput.value).toBe(cipher.login.username); + }); + + it("configures CopyClickDirective for the username", () => { + const usernameCopyButton = usernameField.query(By.directive(CopyClickDirective)); + const usernameCopyClickDirective = usernameCopyButton.injector.get(CopyClickDirective); + + expect(usernameCopyClickDirective.valueToCopy).toBe(cipher.login.username); + }); + }); + + describe("password", () => { + let passwordField: DebugElement; + + beforeEach(() => { + passwordField = fixture.debugElement.queryAll(By.directive(BitFormFieldComponent))[1]; + }); + + it("displays the password", () => { + const passwordInput = passwordField.query(By.css("input")).nativeElement; + + expect(passwordInput.value).toBe(cipher.login.password); + }); + + describe("copy", () => { + it("does not allow copy when `viewPassword` is false", () => { + cipher.viewPassword = false; + fixture.detectChanges(); + + const passwordCopyButton = passwordField.query(By.directive(CopyClickDirective)); + + expect(passwordCopyButton).toBeNull(); + }); + + it("configures CopyClickDirective for the password", () => { + cipher.viewPassword = true; + fixture.detectChanges(); + + const passwordCopyButton = passwordField.query(By.directive(CopyClickDirective)); + const passwordCopyClickDirective = passwordCopyButton.injector.get(CopyClickDirective); + + expect(passwordCopyClickDirective.valueToCopy).toBe(cipher.login.password); + }); + }); + + describe("toggle password", () => { + it("does not allow password to be viewed when `viewPassword` is false", () => { + cipher.viewPassword = false; + fixture.detectChanges(); + + const viewPasswordButton = passwordField.query( + By.directive(BitPasswordInputToggleDirective), + ); + + expect(viewPasswordButton).toBeNull(); + }); + + it("shows password color component", () => { + cipher.viewPassword = true; + fixture.detectChanges(); + + const viewPasswordButton = passwordField.query( + By.directive(BitPasswordInputToggleDirective), + ); + const toggleInputDirective = viewPasswordButton.injector.get( + BitPasswordInputToggleDirective, + ); + + toggleInputDirective.onClick(); + fixture.detectChanges(); + + const passwordColor = passwordField.query(By.directive(ColorPasswordComponent)); + + expect(passwordColor.componentInstance.password).toBe(cipher.login.password); + }); + + it("records event", () => { + cipher.viewPassword = true; + fixture.detectChanges(); + + const viewPasswordButton = passwordField.query( + By.directive(BitPasswordInputToggleDirective), + ); + const toggleInputDirective = viewPasswordButton.injector.get( + BitPasswordInputToggleDirective, + ); + + toggleInputDirective.onClick(); + fixture.detectChanges(); + + expect(collect).toHaveBeenCalledWith( + EventType.Cipher_ClientToggledPasswordVisible, + cipher.id, + false, + cipher.organizationId, + ); + }); + }); + }); + + describe("fido2Credentials", () => { + let fido2Field: DebugElement; + + beforeEach(() => { + fido2Field = fixture.debugElement.queryAll(By.directive(BitFormFieldComponent))[2]; + + // Mock datePipe to avoid timezone related issues within tests + jest.spyOn(component["datePipe"], "transform").mockReturnValue("2/2/24 6:00PM"); + fixture.detectChanges(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("displays the creation date", () => { + const fido2Input = fido2Field.query(By.css("input")).nativeElement; + + expect(fido2Input.value).toBe("dateCreated 2/2/24 6:00PM"); + }); + }); +}); diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index b05d3318c3c..281e187f78b 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -1,24 +1,36 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; import { Component, inject, Input } from "@angular/core"; -import { Observable, shareReplay } from "rxjs"; +import { + BehaviorSubject, + combineLatest, + filter, + map, + Observable, + shareReplay, + switchMap, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { - CardComponent, + BadgeModule, + ColorPasswordModule, FormFieldModule, + IconButtonModule, SectionComponent, SectionHeaderComponent, TypographyModule, - IconButtonModule, - BadgeModule, - ColorPasswordModule, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PremiumUpgradePromptService } from "../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; import { BitTotpCountdownComponent } from "../../components/totp-countdown/totp-countdown.component"; import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; @@ -35,7 +47,6 @@ type TotpCodeValues = { imports: [ CommonModule, JslibModule, - CardComponent, SectionComponent, SectionHeaderComponent, TypographyModule, @@ -48,12 +59,31 @@ type TotpCodeValues = { ], }) export class LoginCredentialsViewComponent { - @Input() cipher: CipherView; + @Input() + get cipher(): CipherView { + return this._cipher$.value; + } + set cipher(value: CipherView) { + this._cipher$.next(value); + } + private _cipher$ = new BehaviorSubject(null); - isPremium$: Observable = - this.billingAccountProfileStateService.hasPremiumFromAnySource$.pipe( - shareReplay({ refCount: true, bufferSize: 1 }), - ); + private _userHasPremium$: Observable = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); + + allowTotpGeneration$: Observable = combineLatest([ + this._userHasPremium$, + this._cipher$.pipe(filter((c) => c != null)), + ]).pipe( + map(([userHasPremium, cipher]) => { + // User premium status only applies to personal ciphers, organizationUseTotp applies to organization ciphers + return (userHasPremium && cipher.organizationId == null) || cipher.organizationUseTotp; + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); showPasswordCount: boolean = false; passwordRevealed: boolean = false; totpCodeCopyObj: TotpCodeValues; @@ -64,6 +94,7 @@ export class LoginCredentialsViewComponent { private i18nService: I18nService, private premiumUpgradeService: PremiumUpgradePromptService, private eventCollectionService: EventCollectionService, + private accountService: AccountService, ) {} get fido2CredentialCreationDateValue(): string { diff --git a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts index ed16f3a7cc0..9005ea9674c 100644 --- a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts +++ b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts @@ -12,14 +12,16 @@ import { CardComponent, BitFormFieldComponent } from "@bitwarden/components"; * A thin wrapper around the `bit-card` component that disables the bottom border for the last form field. */ export class ReadOnlyCipherCardComponent implements AfterViewInit { - @ContentChildren(BitFormFieldComponent) formFields: QueryList; + @ContentChildren(BitFormFieldComponent) formFields?: QueryList; ngAfterViewInit(): void { // Disable the bottom border for the last form field - if (this.formFields.last) { + if (this.formFields?.last) { // Delay model update until next change detection cycle setTimeout(() => { - this.formFields.last.disableReadOnlyBorder = true; + if (this.formFields) { + this.formFields.last.disableReadOnlyBorder = true; + } }); } } diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts index 7f553dbe58b..d597f4d9408 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html index ec4580e31d3..e98905e8808 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html @@ -5,8 +5,14 @@

{{ "personalDetails" | i18n }}

- {{ "name" | i18n }} - + {{ "name" | i18n }} + - {{ "username" | i18n }} - + {{ "username" | i18n }} + - {{ "company" | i18n }} - + {{ "company" | i18n }} + - {{ "passportNumber" | i18n }} + {{ + "passportNumber" | i18n + }} {{ "identification" | i18n }} > - {{ "licenseNumber" | i18n }} - + {{ + "licenseNumber" | i18n + }} + - {{ "phone" | i18n }} - + {{ "phone" | i18n }} + - {{ "address" | i18n }} + {{ "address" | i18n }}

[ngClass]="{ 'bwi-eye': !showPrivateKey, 'bwi-eye-slash': showPrivateKey }" > -
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 a3a9c929159..02fa8076086 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -1,7 +1,10 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { NgForm } from "@angular/forms"; import { sshagent as sshAgent } from "desktop_native/napi"; +import { lastValueFrom } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; @@ -16,12 +19,12 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui"; import { PasswordRepromptService } from "@bitwarden/vault"; const BroadcasterSubscriptionId = "AddEditComponent"; @@ -50,12 +53,12 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On private ngZone: NgZone, logService: LogService, organizationService: OrganizationService, - sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, configService: ConfigService, - private toastService: ToastService, + toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, + sdkService: SdkService, ) { super( cipherService, @@ -71,12 +74,13 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On logService, passwordRepromptService, organizationService, - sendApiService, dialogService, window, datePipe, configService, cipherAuthorizationService, + toastService, + sdkService, ); } @@ -111,9 +115,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On ) { this.cipher = null; } - // 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.load(); + + await super.load(); } onWindowHidden() { @@ -145,62 +148,69 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On ); } - async generateSshKey() { - const sshKey = await ipc.platform.sshAgent.generateKey("ed25519"); - this.cipher.sshKey.privateKey = sshKey.privateKey; - this.cipher.sshKey.publicKey = sshKey.publicKey; - this.cipher.sshKey.keyFingerprint = sshKey.keyFingerprint; - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("sshKeyGenerated"), - }); - } - - async importSshKeyFromClipboard() { + async importSshKeyFromClipboard(password: string = "") { const key = await this.platformUtilsService.readFromClipboard(); - const parsedKey = await ipc.platform.sshAgent.importKey(key, ""); - if (parsedKey == null || parsedKey.status === sshAgent.SshKeyImportStatus.ParsingError) { + const parsedKey = await ipc.platform.sshAgent.importKey(key, password); + if (parsedKey == null) { this.toastService.showToast({ variant: "error", title: "", message: this.i18nService.t("invalidSshKey"), }); return; - } else if (parsedKey.status === sshAgent.SshKeyImportStatus.UnsupportedKeyType) { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("sshKeyTypeUnsupported"), - }); - } else if ( - parsedKey.status === sshAgent.SshKeyImportStatus.PasswordRequired || - parsedKey.status === sshAgent.SshKeyImportStatus.WrongPassword - ) { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("sshKeyPasswordUnsupported"), - }); - return; - } else { - this.cipher.sshKey.privateKey = parsedKey.sshKey.privateKey; - this.cipher.sshKey.publicKey = parsedKey.sshKey.publicKey; - this.cipher.sshKey.keyFingerprint = parsedKey.sshKey.keyFingerprint; - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("sshKeyPasted"), - }); } - } - async typeChange() { - if (this.cipher.type === CipherType.SshKey) { - await this.generateSshKey(); + switch (parsedKey.status) { + case sshAgent.SshKeyImportStatus.ParsingError: + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("invalidSshKey"), + }); + return; + case sshAgent.SshKeyImportStatus.UnsupportedKeyType: + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("sshKeyTypeUnsupported"), + }); + return; + case sshAgent.SshKeyImportStatus.PasswordRequired: + case sshAgent.SshKeyImportStatus.WrongPassword: + if (password !== "") { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("sshKeyWrongPassword"), + }); + } else { + password = await this.getSshKeyPassword(); + if (password === "") { + return; + } + await this.importSshKeyFromClipboard(password); + } + return; + default: + this.cipher.sshKey.privateKey = parsedKey.sshKey.privateKey; + this.cipher.sshKey.publicKey = parsedKey.sshKey.publicKey; + this.cipher.sshKey.keyFingerprint = parsedKey.sshKey.keyFingerprint; + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("sshKeyPasted"), + }); } } + async getSshKeyPassword(): Promise { + const dialog = this.dialogService.open(SshKeyPasswordPromptComponent, { + ariaModal: true, + }); + + return await lastValueFrom(dialog.closed); + } + truncateString(value: string, length: number) { return value.length > length ? value.substring(0, length) + "..." : value; } 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 new file mode 100644 index 00000000000..d16f8e4f1cb --- /dev/null +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html @@ -0,0 +1,36 @@ + + {{ "generator" | i18n }} + + + + + + + + + + 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 new file mode 100644 index 00000000000..ae6f031005e --- /dev/null +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -0,0 +1,75 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + ButtonModule, + DialogModule, + DialogService, + ItemModule, + LinkModule, +} from "@bitwarden/components"; +import { + CredentialGeneratorHistoryDialogComponent, + GeneratorModule, +} from "@bitwarden/generator-components"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; +import { CipherFormGeneratorComponent } from "@bitwarden/vault"; + +type CredentialGeneratorParams = { + onCredentialGenerated: (value?: string) => void; + type: "password" | "username"; +}; + +@Component({ + standalone: true, + selector: "credential-generator-dialog", + templateUrl: "credential-generator-dialog.component.html", + imports: [ + CipherFormGeneratorComponent, + CommonModule, + DialogModule, + ButtonModule, + JslibModule, + GeneratorModule, + ItemModule, + LinkModule, + ], +}) +export class CredentialGeneratorDialogComponent { + credentialValue?: string; + buttonLabel?: string; + + constructor( + @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, + private dialogService: DialogService, + ) {} + + algorithm = (selected: AlgorithmInfo) => { + this.buttonLabel = selected.useGeneratedValue; + }; + + applyCredentials = () => { + this.data.onCredentialGenerated(this.credentialValue); + }; + + clearCredentials = () => { + this.data.onCredentialGenerated(); + }; + + onCredentialGenerated = (value: string) => { + this.credentialValue = value; + }; + + openHistoryDialog = () => { + // open history dialog + this.dialogService.open(CredentialGeneratorHistoryDialogComponent); + }; + + static open = (dialogService: DialogService, data: CredentialGeneratorParams) => { + dialogService.open(CredentialGeneratorDialogComponent, { + data, + }); + }; +} 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 bb89cd230ca..92c75e30417 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/organization-filter.component"; diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html index c3dcd191dfc..55e10980ad1 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html @@ -82,6 +82,7 @@

  • +
    +
    + {{ "verificationCodeTotp" | i18n }} + + {{ "organizationUpgradeRequired" | i18n }} + + +
    +
    @@ -638,33 +648,35 @@

    - - {{ "loggedInAsEmailOn" | i18n: email : webVaultHostname }} - - -
    - -
    - - -
    - diff --git a/apps/web/src/app/auth/lock.component.ts b/apps/web/src/app/auth/lock.component.ts deleted file mode 100644 index b83723bca47..00000000000 --- a/apps/web/src/app/auth/lock.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Component, OnInit, inject } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; - -import { SharedModule } from "../shared"; - -@Component({ - selector: "app-lock", - templateUrl: "lock.component.html", - standalone: true, - imports: [SharedModule], -}) -export class LockComponent extends BaseLockComponent implements OnInit { - formBuilder = inject(FormBuilder); - - formGroup = this.formBuilder.group({ - masterPassword: ["", { validators: Validators.required, updateOn: "submit" }], - }); - - get masterPasswordFormControl() { - return this.formGroup.controls.masterPassword; - } - - async ngOnInit() { - await super.ngOnInit(); - - this.masterPasswordFormControl.setValue(this.masterPassword); - - this.onSuccessfulSubmit = async () => { - await this.router.navigateByUrl(this.successRoute); - }; - } - - async superSubmit() { - await super.submit(); - } - - submit = async () => { - this.formGroup.markAllAsTouched(); - - if (this.formGroup.invalid) { - return; - } - - this.masterPassword = this.masterPasswordFormControl.value; - await this.superSubmit(); - }; -} diff --git a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options.component.html b/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.html similarity index 100% rename from apps/web/src/app/auth/login/login-decryption-options/login-decryption-options.component.html rename to apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.html diff --git a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options.component.ts b/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.ts similarity index 78% rename from apps/web/src/app/auth/login/login-decryption-options/login-decryption-options.component.ts rename to apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.ts index 991fe8b5971..5eb72503b90 100644 --- a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options.component.ts +++ b/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.ts @@ -1,14 +1,14 @@ import { Component, inject } from "@angular/core"; -import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component"; +import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component"; import { RouterService } from "../../../core"; import { AcceptOrganizationInviteService } from "../../organization-invite/accept-organization.service"; @Component({ selector: "web-login-decryption-options", - templateUrl: "login-decryption-options.component.html", + templateUrl: "login-decryption-options-v1.component.html", }) -export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent { +export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 { protected routerService = inject(RouterService); protected acceptOrganizationInviteService = inject(AcceptOrganizationInviteService); diff --git a/apps/web/src/app/auth/login/login-v1.component.html b/apps/web/src/app/auth/login/login-v1.component.html index 4f8ea93bbdd..5b3c2a99424 100644 --- a/apps/web/src/app/auth/login/login-v1.component.html +++ b/apps/web/src/app/auth/login/login-v1.component.html @@ -92,7 +92,7 @@ -
    +
    +
    +
    - -

    + +

    @@ -11,36 +11,3 @@

    -
    -
    -
    -

    {{ "joinOrganization" | i18n }}

    -
    -
    -

    - {{ orgName$ | async }} - {{ email }} -

    -

    {{ "joinOrganizationDesc" | i18n }}

    -
    - -
    -
    -
    -
    -
    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 82f24974e25..660b2759988 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; @@ -54,20 +56,14 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { async unauthedHandler(qParams: Params): Promise { const invite = OrganizationInvite.fromParams(qParams); await this.acceptOrganizationInviteService.setOrganizationInvitation(invite); - await this.accelerateInviteAcceptIfPossible(invite); + await this.navigateInviteAcceptance(invite); } /** * In certain scenarios, we want to accelerate the user through the accept org invite process * For example, if the user has a BW account already, we want them to be taken to login instead of creation. */ - private async accelerateInviteAcceptIfPossible(invite: OrganizationInvite): Promise { - // if orgUserHasExistingUser is null, we can't determine the user's status - // so we don't want to accelerate the process - if (invite.orgUserHasExistingUser == null) { - return; - } - + private async navigateInviteAcceptance(invite: OrganizationInvite): Promise { // if user exists, send user to login if (invite.orgUserHasExistingUser) { await this.router.navigate(["/login"], { 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 a5bd8ae3b07..b3709a15882 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { FakeGlobalStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; import { MockProxy, mock } from "jest-mock-extended"; 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 6d00f58f447..a964d676159 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable } from "@angular/core"; import { BehaviorSubject, firstValueFrom, map } from "rxjs"; diff --git a/apps/web/src/app/auth/organization-invite/organization-invite.ts b/apps/web/src/app/auth/organization-invite/organization-invite.ts index ec90fe96d5e..65414113e74 100644 --- a/apps/web/src/app/auth/organization-invite/organization-invite.ts +++ b/apps/web/src/app/auth/organization-invite/organization-invite.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Params } from "@angular/router"; import { Jsonify } from "type-fest"; diff --git a/apps/web/src/app/auth/recover-delete.component.ts b/apps/web/src/app/auth/recover-delete.component.ts index 04c3eb1df25..6b45421911d 100644 --- a/apps/web/src/app/auth/recover-delete.component.ts +++ b/apps/web/src/app/auth/recover-delete.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; 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 50595460966..a10413b9bd2 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; diff --git a/apps/web/src/app/auth/register-form/register-form.component.ts b/apps/web/src/app/auth/register-form/register-form.component.ts index b44ce1843ca..7d3e6dbd00e 100644 --- a/apps/web/src/app/auth/register-form/register-form.component.ts +++ b/apps/web/src/app/auth/register-form/register-form.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; 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 eed88476e27..7e1be937a22 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { combineLatest, from, lastValueFrom, map, Observable } from "rxjs"; 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 e20245bfa00..cf6d2417029 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, 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 734df682957..b05b2e46982 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 @@ -1,8 +1,9 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request"; import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request"; @@ -12,7 +13,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-email", 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 4d1adddd183..1abea314b50 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,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { Observable } from "rxjs"; 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 dcaf38ee29e..57ca0e0ecfc 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,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 c7c67416e18..aa5cfa3c1dc 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; 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 e6b69807339..d2a887c9b98 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.html +++ b/apps/web/src/app/auth/settings/account/profile.component.html @@ -37,7 +37,7 @@
    - {{ "accountIsManagedMessage" | i18n: managingOrganization?.name }} + {{ "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 57f1b7dadde..4f4920270f0 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { firstValueFrom, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs"; 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 f5f3e80b6bb..8f0f195440a 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; @@ -7,7 +9,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; @@ -23,7 +24,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; 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 3b387b3c1ef..1180c1a3542 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 @@ -1,11 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, OnInit, Inject } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -19,6 +19,8 @@ type EmergencyAccessConfirmDialogData = { userId: string; /** traces a unique emergency request */ emergencyAccessId: string; + /** user public key */ + publicKey: Uint8Array; }; @Component({ selector: "emergency-access-confirm", @@ -34,7 +36,6 @@ export class EmergencyAccessConfirmComponent implements OnInit { constructor( @Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData, private formBuilder: FormBuilder, - private apiService: ApiService, private keyService: KeyService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, private logService: LogService, @@ -43,13 +44,12 @@ export class EmergencyAccessConfirmComponent implements OnInit { async ngOnInit() { try { - const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId); - if (publicKeyResponse != null) { - const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); - const fingerprint = await this.keyService.getFingerprint(this.params.userId, publicKey); - if (fingerprint != null) { - this.fingerprint = fingerprint.join("-"); - } + const fingerprint = await this.keyService.getFingerprint( + this.params.userId, + this.params.publicKey, + ); + if (fingerprint != null) { + this.fingerprint = fingerprint.join("-"); } } catch (e) { this.logService.error(e); 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 fa5e80c81f5..47ec38691aa 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; 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 d8cedd5bd43..73e32add5c2 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,15 +1,20 @@ +// 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 { lastValueFrom, Observable, firstValueFrom } from "rxjs"; +import { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; 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 { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService, ToastService } from "@bitwarden/components"; import { EmergencyAccessService } from "../../emergency-access"; @@ -67,8 +72,14 @@ export class EmergencyAccessComponent implements OnInit { billingAccountProfileStateService: BillingAccountProfileStateService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, private toastService: ToastService, + private apiService: ApiService, + private accountService: AccountService, ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); } async ngOnInit() { @@ -139,6 +150,9 @@ export class EmergencyAccessComponent implements OnInit { return; } + const publicKeyResponse = await this.apiService.getUserPublicKey(contact.granteeId); + const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); + const autoConfirm = await firstValueFrom( this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$, ); @@ -148,11 +162,12 @@ export class EmergencyAccessComponent implements OnInit { name: this.userNamePipe.transform(contact), emergencyAccessId: contact.id, userId: contact?.granteeId, + publicKey, }, }); const result = await lastValueFrom(dialogRef.closed); if (result === EmergencyAccessConfirmDialogResult.Confirmed) { - await this.emergencyAccessService.confirm(contact.id, contact.granteeId); + await this.emergencyAccessService.confirm(contact.id, contact.granteeId, publicKey); updateUser(); this.toastService.showToast({ variant: "success", @@ -163,7 +178,11 @@ export class EmergencyAccessComponent implements OnInit { return; } - this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId); + this.actionPromise = this.emergencyAccessService.confirm( + contact.id, + contact.granteeId, + publicKey, + ); await this.actionPromise; updateUser(); 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 c567508e050..4e00c962ffd 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 @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; @@ -6,17 +8,15 @@ import { takeUntil } from "rxjs"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { KdfType } from "@bitwarden/common/platform/enums"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { EmergencyAccessService } from "../../../emergency-access"; diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts index c048edef458..7506f6c5d0b 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts @@ -1,17 +1,25 @@ +// 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 { ActivatedRoute, Router } from "@angular/router"; import { ModalService } from "@bitwarden/angular/services/modal.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwarden/vault"; import { EmergencyAccessService } from "../../../emergency-access"; import { EmergencyAccessAttachmentsComponent } from "../attachments/emergency-access-attachments.component"; import { EmergencyAddEditCipherComponent } from "./emergency-add-edit-cipher.component"; +import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component"; @Component({ selector: "emergency-access-view", templateUrl: "emergency-access-view.component.html", + providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }], }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil export class EmergencyAccessViewComponent implements OnInit { @@ -29,6 +37,8 @@ export class EmergencyAccessViewComponent implements OnInit { private router: Router, private route: ActivatedRoute, private emergencyAccessService: EmergencyAccessService, + private configService: ConfigService, + private dialogService: DialogService, ) {} ngOnInit() { @@ -47,6 +57,19 @@ export class EmergencyAccessViewComponent implements OnInit { } async selectCipher(cipher: CipherView) { + const browserRefreshEnabled = await this.configService.getFeatureFlag( + FeatureFlag.ExtensionRefresh, + ); + + if (browserRefreshEnabled) { + EmergencyViewDialogComponent.open(this.dialogService, { + cipher, + }); + return; + } + + // FIXME PM-15385: Remove below dialog service logic once extension refresh is live. + // eslint-disable-next-line const [_, childComponent] = await this.modalService.openViewRef( EmergencyAddEditCipherComponent, diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts index f760b76638f..78e3b805eb8 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts @@ -1,5 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DatePipe } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { CollectionService } from "@bitwarden/admin-console/common"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -13,13 +15,13 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; 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 { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -29,7 +31,7 @@ import { AddEditComponent as BaseAddEditComponent } from "../../../../vault/indi selector: "app-org-vault-add-edit", templateUrl: "../../../../vault/individual-vault/add-edit.component.html", }) -export class EmergencyAddEditCipherComponent extends BaseAddEditComponent { +export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implements OnInit { originalCipher: Cipher = null; viewOnly = true; protected override componentName = "app-org-vault-add-edit"; @@ -50,12 +52,13 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent { passwordRepromptService: PasswordRepromptService, organizationService: OrganizationService, logService: LogService, - sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, configService: ConfigService, billingAccountProfileStateService: BillingAccountProfileStateService, cipherAuthorizationService: CipherAuthorizationService, + toastService: ToastService, + sdkService: SdkService, ) { super( cipherService, @@ -73,12 +76,13 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent { organizationService, logService, passwordRepromptService, - sendApiService, dialogService, datePipe, configService, billingAccountProfileStateService, cipherAuthorizationService, + toastService, + sdkService, ); } @@ -86,6 +90,14 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent { this.title = this.i18nService.t("viewItem"); } + async ngOnInit(): Promise { + await super.ngOnInit(); + // The base component `ngOnInit` calculates the `viewOnly` property based on cipher properties + // In the case of emergency access, `viewOnly` should always be true, set it manually here after + // the base `ngOnInit` is complete. + this.viewOnly = true; + } + protected async loadCipher() { return Promise.resolve(this.originalCipher); } diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.html b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.html new file mode 100644 index 00000000000..be38e1d9505 --- /dev/null +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.html @@ -0,0 +1,13 @@ + + + {{ title }} + +
    + +
    + + + +
    diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts new file mode 100644 index 00000000000..0ad7eef81be --- /dev/null +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -0,0 +1,115 @@ +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { mock } from "jest-mock-extended"; + +import { CollectionService } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; + +import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component"; + +describe("EmergencyViewDialogComponent", () => { + let component: EmergencyViewDialogComponent; + let fixture: ComponentFixture; + + const open = jest.fn(); + const close = jest.fn(); + + const mockCipher = { + id: "cipher1", + name: "Cipher", + type: CipherType.Login, + login: { uris: [] }, + card: {}, + } as CipherView; + + const accountService: FakeAccountService = mockAccountServiceWith(Utils.newGuid() as UserId); + + beforeEach(async () => { + open.mockClear(); + close.mockClear(); + + await TestBed.configureTestingModule({ + imports: [EmergencyViewDialogComponent, NoopAnimationsModule], + providers: [ + { provide: OrganizationService, useValue: mock() }, + { provide: CollectionService, useValue: mock() }, + { provide: FolderService, useValue: mock() }, + { provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } }, + { provide: DialogService, useValue: { open } }, + { provide: DialogRef, useValue: { close } }, + { provide: DIALOG_DATA, useValue: { cipher: mockCipher } }, + { provide: AccountService, useValue: accountService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(EmergencyViewDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("creates", () => { + expect(component).toBeTruthy(); + }); + + it("opens dialog", () => { + EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { cipher: mockCipher }); + + expect(open).toHaveBeenCalled(); + }); + + it("closes the dialog", () => { + EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { cipher: mockCipher }); + fixture.detectChanges(); + + const cancelButton = fixture.debugElement.queryAll(By.css("button")).pop(); + + cancelButton.nativeElement.click(); + + expect(close).toHaveBeenCalled(); + }); + + describe("updateTitle", () => { + it("sets login title", () => { + mockCipher.type = CipherType.Login; + + component["updateTitle"](); + + expect(component["title"]).toBe("viewItemType typelogin"); + }); + + it("sets card title", () => { + mockCipher.type = CipherType.Card; + + component["updateTitle"](); + + expect(component["title"]).toBe("viewItemType typecard"); + }); + + it("sets identity title", () => { + mockCipher.type = CipherType.Identity; + + component["updateTitle"](); + + expect(component["title"]).toBe("viewItemType typeidentity"); + }); + + it("sets note title", () => { + mockCipher.type = CipherType.SecureNote; + + component["updateTitle"](); + + expect(component["title"]).toBe("viewItemType note"); + }); + }); +}); diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts new file mode 100644 index 00000000000..68423c50d88 --- /dev/null +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts @@ -0,0 +1,90 @@ +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; +import { CipherViewComponent } from "@bitwarden/vault"; + +import { WebViewPasswordHistoryService } from "../../../../vault/services/web-view-password-history.service"; + +export interface EmergencyViewDialogParams { + /** The cipher being viewed. */ + cipher: CipherView; +} + +/** Stubbed class, premium upgrade is not applicable for emergency viewing */ +class PremiumUpgradePromptNoop implements PremiumUpgradePromptService { + async promptForPremium() { + return Promise.resolve(); + } +} + +@Component({ + selector: "app-emergency-view-dialog", + templateUrl: "emergency-view-dialog.component.html", + standalone: true, + imports: [ButtonModule, CipherViewComponent, DialogModule, CommonModule, JslibModule], + providers: [ + { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, + { provide: PremiumUpgradePromptService, useClass: PremiumUpgradePromptNoop }, + ], +}) +export class EmergencyViewDialogComponent { + /** + * The title of the dialog. Updates based on the cipher type. + * @protected + */ + protected title: string = ""; + + constructor( + @Inject(DIALOG_DATA) protected params: EmergencyViewDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, + ) { + this.updateTitle(); + } + + get cipher(): CipherView { + return this.params.cipher; + } + + cancel = () => { + this.dialogRef.close(); + }; + + private updateTitle() { + const partOne = "viewItemType"; + + const type = this.cipher.type; + + switch (type) { + case CipherType.Login: + this.title = this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLowerCase()); + break; + case CipherType.Card: + this.title = this.i18nService.t(partOne, this.i18nService.t("typeCard").toLowerCase()); + break; + case CipherType.Identity: + this.title = this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLowerCase()); + break; + case CipherType.SecureNote: + this.title = this.i18nService.t(partOne, this.i18nService.t("note").toLowerCase()); + break; + } + } + + /** + * Opens the EmergencyViewDialog. + */ + static open(dialogService: DialogService, params: EmergencyViewDialogParams) { + return dialogService.open(EmergencyViewDialogComponent, { + data: params, + }); + } +} diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index d171bc35617..f603b2ebc94 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts index 17954b3ee8b..027544c1db6 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { FormGroup, FormControl, Validators } from "@angular/forms"; @@ -5,14 +7,12 @@ import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { KdfRequest } from "@bitwarden/common/models/request/kdf.request"; 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 { KdfType } from "@bitwarden/common/platform/enums"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; +import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-kdf-confirmation", diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index 45ceaeccd07..3c392795ef4 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -1,16 +1,18 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, ValidatorFn, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; -import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; +import { DialogService } from "@bitwarden/components"; import { + KdfConfigService, Argon2KdfConfig, DEFAULT_KDF_CONFIG, KdfConfig, PBKDF2KdfConfig, -} from "@bitwarden/common/auth/models/domain/kdf-config"; -import { KdfType } from "@bitwarden/common/platform/enums"; -import { DialogService } from "@bitwarden/components"; + KdfType, +} from "@bitwarden/key-management"; import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.component"; diff --git a/apps/web/src/app/auth/settings/security/device-management.component.html b/apps/web/src/app/auth/settings/security/device-management.component.html new file mode 100644 index 00000000000..37827a33afe --- /dev/null +++ b/apps/web/src/app/auth/settings/security/device-management.component.html @@ -0,0 +1,103 @@ + +
    +
    +

    {{ "devices" | i18n }}

    + + +

    {{ "aDeviceIs" | i18n }}

    +
    + +
    +
    + +

    {{ "deviceListDescriptionTemp" | i18n }}

    + +
    + +
    + + + + + {{ col.title }} + + + + + +
    + +
    +
    + + + {{ row.displayName }} + + + + {{ "needsApproval" | i18n }} + + + + {{ row.displayName }} + + {{ "trusted" | i18n }} + + +
    + + + {{ + "currentSession" | i18n + }} + {{ + "requestPending" | i18n + }} + + {{ row.firstLogin | date: "medium" }} + +
    +
    +
    diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts new file mode 100644 index 00000000000..e22122ad9ae --- /dev/null +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -0,0 +1,243 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { combineLatest, firstValueFrom } from "rxjs"; + +import { LoginApprovalComponent } from "@bitwarden/auth/angular"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { + DevicePendingAuthRequest, + DeviceResponse, +} from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; +import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; +import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { + DialogService, + ToastService, + TableDataSource, + TableModule, + PopoverModule, +} from "@bitwarden/components"; + +import { SharedModule } from "../../../shared"; + +interface DeviceTableData { + id: string; + type: DeviceType; + displayName: string; + loginStatus: string; + firstLogin: Date; + trusted: boolean; + devicePendingAuthRequest: DevicePendingAuthRequest | null; + hasPendingAuthRequest: boolean; +} + +/** + * Provides a table of devices and allows the user to log out, approve or remove a device + */ +@Component({ + selector: "app-device-management", + templateUrl: "./device-management.component.html", + standalone: true, + imports: [CommonModule, SharedModule, TableModule, PopoverModule], +}) +export class DeviceManagementComponent { + protected readonly tableId = "device-management-table"; + protected dataSource = new TableDataSource(); + protected currentDevice: DeviceView | undefined; + protected loading = true; + protected asyncActionLoading = false; + + constructor( + private i18nService: I18nService, + private devicesService: DevicesServiceAbstraction, + private dialogService: DialogService, + private toastService: ToastService, + private validationService: ValidationService, + ) { + combineLatest([this.devicesService.getCurrentDevice$(), this.devicesService.getDevices$()]) + .pipe(takeUntilDestroyed()) + .subscribe({ + next: ([currentDevice, devices]: [DeviceResponse, Array]) => { + this.currentDevice = new DeviceView(currentDevice); + + this.dataSource.data = devices.map((device: DeviceView): DeviceTableData => { + return { + id: device.id, + type: device.type, + displayName: this.getHumanReadableDeviceType(device.type), + loginStatus: this.getLoginStatus(device), + firstLogin: new Date(device.creationDate), + trusted: device.response.isTrusted, + devicePendingAuthRequest: device.response.devicePendingAuthRequest, + hasPendingAuthRequest: this.hasPendingAuthRequest(device.response), + }; + }); + + this.loading = false; + }, + error: () => { + this.loading = false; + }, + }); + } + + /** + * Column configuration for the table + */ + protected readonly columnConfig = [ + { + name: "displayName", + title: this.i18nService.t("device"), + headerClass: "tw-w-1/3", + sortable: true, + }, + { + name: "loginStatus", + title: this.i18nService.t("loginStatus"), + headerClass: "tw-w-1/3", + sortable: true, + }, + { + name: "firstLogin", + title: this.i18nService.t("firstLogin"), + headerClass: "tw-w-1/3", + sortable: true, + }, + ]; + + /** + * Get the icon for a device type + * @param type - The device type + * @returns The icon for the device type + */ + getDeviceIcon(type: DeviceType): string { + const defaultIcon = "bwi bwi-desktop"; + const categoryIconMap: Record = { + webVault: "bwi bwi-browser", + desktop: "bwi bwi-desktop", + mobile: "bwi bwi-mobile", + cli: "bwi bwi-cli", + extension: "bwi bwi-puzzle", + sdk: "bwi bwi-desktop", + }; + + const metadata = DeviceTypeMetadata[type]; + return metadata ? (categoryIconMap[metadata.category] ?? defaultIcon) : defaultIcon; + } + + /** + * Get the login status of a device + * It will return the current session if the device is the current device + * It will return the date of the pending auth request when available + * @param device - The device + * @returns The login status + */ + private getLoginStatus(device: DeviceView): string { + if (this.isCurrentDevice(device)) { + return this.i18nService.t("currentSession"); + } + + if (device.response.devicePendingAuthRequest?.creationDate) { + return this.i18nService.t("requestPending"); + } + + return ""; + } + + /** + * Get a human readable device type from the DeviceType enum + * @param type - The device type + * @returns The human readable device type + */ + private getHumanReadableDeviceType(type: DeviceType): string { + const metadata = DeviceTypeMetadata[type]; + if (!metadata) { + return this.i18nService.t("unknownDevice"); + } + + // If the platform is "Unknown" translate it since it is not a proper noun + const platform = + metadata.platform === "Unknown" ? this.i18nService.t("unknown") : metadata.platform; + const category = this.i18nService.t(metadata.category); + return platform ? `${category} - ${platform}` : category; + } + + /** + * Check if a device is the current device + * @param device - The device or device table data + * @returns True if the device is the current device, false otherwise + */ + protected isCurrentDevice(device: DeviceView | DeviceTableData): boolean { + return "response" in device + ? device.id === this.currentDevice?.id + : device.id === this.currentDevice?.id; + } + + /** + * Check if a device has a pending auth request + * @param device - The device response + * @returns True if the device has a pending auth request, false otherwise + */ + private hasPendingAuthRequest(device: DeviceResponse): boolean { + return ( + device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null + ); + } + + /** + * Open a dialog to approve or deny a pending auth request for a device + */ + async managePendingAuthRequest(device: DeviceTableData) { + if (device.devicePendingAuthRequest === undefined || device.devicePendingAuthRequest === null) { + return; + } + + const dialogRef = LoginApprovalComponent.open(this.dialogService, { + notificationId: device.devicePendingAuthRequest.id, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result !== undefined && typeof result === "boolean") { + // auth request approved or denied so reset + device.devicePendingAuthRequest = null; + device.hasPendingAuthRequest = false; + } + } + + /** + * Remove a device + * @param device - The device + */ + protected async removeDevice(device: DeviceTableData) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "removeDevice" }, + content: { key: "removeDeviceConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + this.asyncActionLoading = true; + await firstValueFrom(this.devicesService.deactivateDevice$(device.id)); + this.asyncActionLoading = false; + + // Remove the device from the data source + this.dataSource.data = this.dataSource.data.filter((d) => d.id !== device.id); + + this.toastService.showToast({ + title: "", + message: this.i18nService.t("deviceRemoved"), + variant: "success", + }); + } catch (error) { + this.validationService.showError(error); + } + } +} diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index a8892def5c1..78b48bea8b4 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; diff --git a/apps/web/src/app/auth/settings/security/security-routing.module.ts b/apps/web/src/app/auth/settings/security/security-routing.module.ts index ca1d7c6aa65..6ed21605184 100644 --- a/apps/web/src/app/auth/settings/security/security-routing.module.ts +++ b/apps/web/src/app/auth/settings/security/security-routing.module.ts @@ -2,8 +2,9 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { ChangePasswordComponent } from "../change-password.component"; -import { TwoFactorSetupComponent } from "../two-factor-setup.component"; +import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component"; +import { DeviceManagementComponent } from "./device-management.component"; import { SecurityKeysComponent } from "./security-keys.component"; import { SecurityComponent } from "./security.component"; @@ -29,6 +30,11 @@ const routes: Routes = [ component: SecurityKeysComponent, data: { titleId: "keys" }, }, + { + path: "device-management", + component: DeviceManagementComponent, + data: { titleId: "devices" }, + }, ], }, ]; diff --git a/apps/web/src/app/auth/settings/security/security.component.html b/apps/web/src/app/auth/settings/security/security.component.html index 25459faeacc..6bd7c1daf36 100644 --- a/apps/web/src/app/auth/settings/security/security.component.html +++ b/apps/web/src/app/auth/settings/security/security.component.html @@ -4,6 +4,7 @@ {{ "masterPassword" | i18n }} {{ "twoStepLogin" | i18n }} + {{ "devices" | i18n }} {{ "keys" | i18n }} diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 1df8145a917..d643b565df2 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @Component({ selector: "app-security", @@ -9,7 +10,10 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use export class SecurityComponent implements OnInit { showChangePassword = true; - constructor(private userVerificationService: UserVerificationService) {} + constructor( + private userVerificationService: UserVerificationService, + private configService: ConfigService, + ) {} async ngOnInit() { this.showChangePassword = await this.userVerificationService.hasMasterPassword(); diff --git a/apps/web/src/app/auth/settings/two-factor-recovery.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-recovery.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-recovery.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts similarity index 95% rename from apps/web/src/app/auth/settings/two-factor-recovery.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts index 35f6f686d95..5305aa19487 100644 --- a/apps/web/src/app/auth/settings/two-factor-recovery.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-authenticator.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts similarity index 94% rename from apps/web/src/app/auth/settings/two-factor-authenticator.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts index da5378f4790..de187d2c33e 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; @@ -18,7 +20,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService, ToastService } from "@bitwarden/components"; -import { TwoFactorBaseComponent } from "./two-factor-base.component"; +import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; // NOTE: There are additional options available but these are just the ones we are current using. // See: https://github.com/neocotic/qrious#examples @@ -35,11 +37,11 @@ declare global { } @Component({ - selector: "app-two-factor-authenticator", - templateUrl: "two-factor-authenticator.component.html", + selector: "app-two-factor-setup-authenticator", + templateUrl: "two-factor-setup-authenticator.component.html", }) -export class TwoFactorAuthenticatorComponent - extends TwoFactorBaseComponent +export class TwoFactorSetupAuthenticatorComponent + extends TwoFactorSetupMethodBaseComponent implements OnInit, OnDestroy { @Output() onChangeStatus = new EventEmitter(); @@ -200,7 +202,7 @@ export class TwoFactorAuthenticatorComponent dialogService: DialogService, config: DialogConfig>, ) { - return dialogService.open(TwoFactorAuthenticatorComponent, config); + return dialogService.open(TwoFactorSetupAuthenticatorComponent, config); } async launchExternalUrl(url: string) { diff --git a/apps/web/src/app/auth/settings/two-factor-duo.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-duo.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-duo.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts similarity index 90% rename from apps/web/src/app/auth/settings/two-factor-duo.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts index 1a5b5917108..5319d4fdbad 100644 --- a/apps/web/src/app/auth/settings/two-factor-duo.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; @@ -13,13 +15,16 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { TwoFactorBaseComponent } from "./two-factor-base.component"; +import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; @Component({ - selector: "app-two-factor-duo", - templateUrl: "two-factor-duo.component.html", + selector: "app-two-factor-setup-duo", + templateUrl: "two-factor-setup-duo.component.html", }) -export class TwoFactorDuoComponent extends TwoFactorBaseComponent implements OnInit { +export class TwoFactorSetupDuoComponent + extends TwoFactorSetupMethodBaseComponent + implements OnInit +{ @Output() onChangeStatus: EventEmitter = new EventEmitter(); type = TwoFactorProviderType.Duo; @@ -137,7 +142,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent implements OnI dialogService: DialogService, config: DialogConfig, ) => { - return dialogService.open(TwoFactorDuoComponent, config); + return dialogService.open(TwoFactorSetupDuoComponent, config); }; } diff --git a/apps/web/src/app/auth/settings/two-factor-email.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-email.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-email.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts similarity index 90% rename from apps/web/src/app/auth/settings/two-factor-email.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts index 524b00d114f..bb9de71710e 100644 --- a/apps/web/src/app/auth/settings/two-factor-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; @@ -16,14 +18,17 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { TwoFactorBaseComponent } from "./two-factor-base.component"; +import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; @Component({ - selector: "app-two-factor-email", - templateUrl: "two-factor-email.component.html", + selector: "app-two-factor-setup-email", + templateUrl: "two-factor-setup-email.component.html", outputs: ["onUpdated"], }) -export class TwoFactorEmailComponent extends TwoFactorBaseComponent implements OnInit { +export class TwoFactorSetupEmailComponent + extends TwoFactorSetupMethodBaseComponent + implements OnInit +{ @Output() onChangeStatus: EventEmitter = new EventEmitter(); type = TwoFactorProviderType.Email; sentEmail: string; @@ -139,6 +144,6 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent implements O dialogService: DialogService, config: DialogConfig>, ) { - return dialogService.open(TwoFactorEmailComponent, config); + return dialogService.open(TwoFactorSetupEmailComponent, config); } } diff --git a/apps/web/src/app/auth/settings/two-factor-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts similarity index 94% rename from apps/web/src/app/auth/settings/two-factor-base.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index 2a6af1df98c..b87b92a965c 100644 --- a/apps/web/src/app/auth/settings/two-factor-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Directive, EventEmitter, Output } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -12,8 +14,11 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +/** + * Base class for two-factor setup components (ex: email, yubikey, webauthn, duo). + */ @Directive() -export abstract class TwoFactorBaseComponent { +export abstract class TwoFactorSetupMethodBaseComponent { @Output() onUpdated = new EventEmitter(); type: TwoFactorProviderType; diff --git a/apps/web/src/app/auth/settings/two-factor-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-webauthn.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts similarity index 93% rename from apps/web/src/app/auth/settings/two-factor-webauthn.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts index 6dfee920991..bd924eca50c 100644 --- a/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject, NgZone } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; @@ -18,7 +20,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { TwoFactorBaseComponent } from "./two-factor-base.component"; +import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; interface Key { id: number; @@ -29,10 +31,10 @@ interface Key { } @Component({ - selector: "app-two-factor-webauthn", - templateUrl: "two-factor-webauthn.component.html", + selector: "app-two-factor-setup-webauthn", + templateUrl: "two-factor-setup-webauthn.component.html", }) -export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { +export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseComponent { type = TwoFactorProviderType.WebAuthn; name: string; keys: Key[]; @@ -213,6 +215,6 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { dialogService: DialogService, config: DialogConfig>, ) { - return dialogService.open(TwoFactorWebAuthnComponent, config); + return dialogService.open(TwoFactorSetupWebAuthnComponent, config); } } diff --git a/apps/web/src/app/auth/settings/two-factor-yubikey.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-yubikey.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-yubikey.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts similarity index 91% rename from apps/web/src/app/auth/settings/two-factor-yubikey.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts index 3b601084c35..b9e168671ea 100644 --- a/apps/web/src/app/auth/settings/two-factor-yubikey.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormArray, FormBuilder, FormControl, FormGroup } from "@angular/forms"; @@ -13,7 +15,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { TwoFactorBaseComponent } from "./two-factor-base.component"; +import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; interface Key { key: string; @@ -21,10 +23,13 @@ interface Key { } @Component({ - selector: "app-two-factor-yubikey", - templateUrl: "two-factor-yubikey.component.html", + selector: "app-two-factor-setup-yubikey", + templateUrl: "two-factor-setup-yubikey.component.html", }) -export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent implements OnInit { +export class TwoFactorSetupYubiKeyComponent + extends TwoFactorSetupMethodBaseComponent + implements OnInit +{ type = TwoFactorProviderType.Yubikey; keys: Key[]; anyKeyHasNfc = false; @@ -169,6 +174,6 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent implements dialogService: DialogService, config: DialogConfig>, ) { - return dialogService.open(TwoFactorYubiKeyComponent, config); + return dialogService.open(TwoFactorSetupYubiKeyComponent, config); } } diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-setup.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts similarity index 86% rename from apps/web/src/app/auth/settings/two-factor-setup.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index 3b8a9edd955..3b20718873d 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core"; import { @@ -8,6 +10,7 @@ import { Subject, Subscription, takeUntil, + switchMap, } from "rxjs"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -16,6 +19,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; @@ -29,13 +33,13 @@ import { ProductTierType } from "@bitwarden/common/billing/enums"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { DialogService } from "@bitwarden/components"; -import { TwoFactorAuthenticatorComponent } from "./two-factor-authenticator.component"; -import { TwoFactorDuoComponent } from "./two-factor-duo.component"; -import { TwoFactorEmailComponent } from "./two-factor-email.component"; import { TwoFactorRecoveryComponent } from "./two-factor-recovery.component"; +import { TwoFactorSetupAuthenticatorComponent } from "./two-factor-setup-authenticator.component"; +import { TwoFactorSetupDuoComponent } from "./two-factor-setup-duo.component"; +import { TwoFactorSetupEmailComponent } from "./two-factor-setup-email.component"; +import { TwoFactorSetupWebAuthnComponent } from "./two-factor-setup-webauthn.component"; +import { TwoFactorSetupYubiKeyComponent } from "./two-factor-setup-yubikey.component"; import { TwoFactorVerifyComponent } from "./two-factor-verify.component"; -import { TwoFactorWebAuthnComponent } from "./two-factor-webauthn.component"; -import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component"; @Component({ selector: "app-two-factor-setup", @@ -67,8 +71,13 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { protected messagingService: MessagingService, protected policyService: PolicyService, billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); } async ngOnInit() { @@ -142,7 +151,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const authComp: DialogRef = TwoFactorAuthenticatorComponent.open( + const authComp: DialogRef = TwoFactorSetupAuthenticatorComponent.open( this.dialogService, { data: result }, ); @@ -160,7 +169,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const yubiComp: DialogRef = TwoFactorYubiKeyComponent.open( + const yubiComp: DialogRef = TwoFactorSetupYubiKeyComponent.open( this.dialogService, { data: result }, ); @@ -177,11 +186,14 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const duoComp: DialogRef = TwoFactorDuoComponent.open(this.dialogService, { - data: { - authResponse: result, + const duoComp: DialogRef = TwoFactorSetupDuoComponent.open( + this.dialogService, + { + data: { + authResponse: result, + }, }, - }); + ); this.twoFactorSetupSubscription = duoComp.componentInstance.onChangeStatus .pipe(first(), takeUntil(this.destroy$)) .subscribe((enabled: boolean) => { @@ -196,7 +208,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const emailComp: DialogRef = TwoFactorEmailComponent.open( + const emailComp: DialogRef = TwoFactorSetupEmailComponent.open( this.dialogService, { data: result, @@ -216,7 +228,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const webAuthnComp: DialogRef = TwoFactorWebAuthnComponent.open( + const webAuthnComp: DialogRef = TwoFactorSetupWebAuthnComponent.open( this.dialogService, { data: result }, ); diff --git a/apps/web/src/app/auth/settings/two-factor-verify.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.html similarity index 100% rename from apps/web/src/app/auth/settings/two-factor-verify.component.html rename to apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.html diff --git a/apps/web/src/app/auth/settings/two-factor-verify.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts similarity index 97% rename from apps/web/src/app/auth/settings/two-factor-verify.component.ts rename to apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts index d41efc9b027..20199492f24 100644 --- a/apps/web/src/app/auth/settings/two-factor-verify.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, EventEmitter, Inject, Output } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; diff --git a/apps/web/src/app/auth/settings/verify-email.component.html b/apps/web/src/app/auth/settings/verify-email.component.html index 60f7e52b9a2..a691c30695c 100644 --- a/apps/web/src/app/auth/settings/verify-email.component.html +++ b/apps/web/src/app/auth/settings/verify-email.component.html @@ -3,7 +3,7 @@ - + (); protected readonly SubscriptionProduct = SubscriptionProduct; protected readonly ProductType = ProductType; + protected trialPaymentOptional$ = this.configService.getFeatureFlag$( + FeatureFlag.TrialPaymentOptional, + ); constructor( protected router: Router, @@ -88,6 +105,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { private registrationFinishService: RegistrationFinishService, private validationService: ValidationService, private loginStrategyService: LoginStrategyServiceAbstraction, + private configService: ConfigService, ) {} async ngOnInit(): Promise { @@ -117,6 +135,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { this.product = this.validProducts.includes(product) ? product : ProductType.PasswordManager; const productTierParam = parseInt(qParams.productTier) as ProductTierType; + this.productTierValue = productTierParam; /** Only show the trial stepper for a subset of types */ const showPasswordManagerStepper = this.stepperProductTypes.includes(productTierParam); @@ -183,6 +202,16 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { } } + async orgNameEntrySubmit(): Promise { + const isTrialPaymentOptional = await firstValueFrom(this.trialPaymentOptional$); + + if (isTrialPaymentOptional) { + await this.createOrganizationOnTrial(); + } else { + await this.conditionallyCreateOrganization(); + } + } + /** Update local details from organization created event */ createdOrganization(event: OrganizationCreatedEvent) { this.orgId = event.organizationId; @@ -190,11 +219,62 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { this.verticalStepper.next(); } + /** create an organization on trial without payment method */ + async createOrganizationOnTrial() { + this.loading = true; + let trialInitiationPath: InitiationPath = "Password Manager trial from marketing website"; + let plan: PlanInformation = { + type: this.getPlanType(), + passwordManagerSeats: 1, + }; + + if (this.product === ProductType.SecretsManager) { + trialInitiationPath = "Secrets Manager trial from marketing website"; + plan = { + ...plan, + subscribeToSecretsManager: true, + isFromSecretsManagerTrial: true, + secretsManagerSeats: 1, + }; + } + + const organization: OrganizationInformation = { + name: this.orgInfoFormGroup.value.name, + billingEmail: this.orgInfoFormGroup.value.billingEmail, + initiationPath: trialInitiationPath, + }; + + const response = await this.organizationBillingService.purchaseSubscriptionNoPaymentMethod({ + organization, + plan, + }); + + this.orgId = response?.id; + this.billingSubLabel = response.name.toString(); + this.loading = false; + this.verticalStepper.next(); + } + /** Move the user to the previous step */ previousStep() { this.verticalStepper.previous(); } + getPlanType() { + switch (this.productTier) { + case ProductTierType.Teams: + return PlanType.TeamsAnnually; + case ProductTierType.Enterprise: + return PlanType.EnterpriseAnnually; + case ProductTierType.Families: + return PlanType.FamiliesAnnually; + case ProductTierType.Free: + return PlanType.Free; + default: + return PlanType.EnterpriseAnnually; + } + } + get isSecretsManagerFree() { return this.product === ProductType.SecretsManager && this.productTier === ProductTierType.Free; } diff --git a/apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts b/apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts index 69d08e627a5..c850cdbd27a 100644 --- a/apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts +++ b/apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; import { ProductType } from "@bitwarden/common/billing/enums"; diff --git a/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts b/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts index ac67c499bb4..6419ddf1e45 100644 --- a/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts +++ b/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; @Component({ diff --git a/apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts b/apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts index b87e75e7f94..9b104ac0bc3 100644 --- a/apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts +++ b/apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input } from "@angular/core"; @Component({ diff --git a/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts b/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts index 20e6c2f849b..955c18fddf2 100644 --- a/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts +++ b/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts index bc354009775..f7c5a9b2b98 100644 --- a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts +++ b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit, ViewChild } from "@angular/core"; import { UntypedFormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts index c02c8aa4158..1effdfa21a7 100644 --- a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts +++ b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Input, OnInit, ViewChild } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.ts b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.ts index 5728f0c7519..678514532ca 100644 --- a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.ts +++ b/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts index c4ee91563b5..61fc7a60035 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { StepperSelectionEvent } from "@angular/cdk/stepper"; import { TitleCasePipe } from "@angular/common"; import { NO_ERRORS_SCHEMA } from "@angular/core"; diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts index 7892283a387..fbe3eb7aa6d 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { StepperSelectionEvent } from "@angular/cdk/stepper"; import { TitleCasePipe } from "@angular/common"; import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.ts b/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.ts index 8a074073dbf..e43eb4e6cda 100644 --- a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.ts +++ b/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { VerticalStep } from "./vertical-step.component"; diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.ts b/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.ts index 745bb5767fe..04197827813 100644 --- a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.ts +++ b/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CdkStepper } from "@angular/cdk/stepper"; import { Component, Input, QueryList } from "@angular/core"; diff --git a/apps/web/src/app/auth/two-factor-auth-duo.component.ts b/apps/web/src/app/auth/two-factor-auth-duo.component.ts index 438acd000f5..b82632008bd 100644 --- a/apps/web/src/app/auth/two-factor-auth-duo.component.ts +++ b/apps/web/src/app/auth/two-factor-auth-duo.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; diff --git a/apps/web/src/app/auth/two-factor.component.ts b/apps/web/src/app/auth/two-factor.component.ts index 691170233c8..eead66468fd 100644 --- a/apps/web/src/app/auth/two-factor.component.ts +++ b/apps/web/src/app/auth/two-factor.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index d47e2c885ec..4ed5369773a 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { first } from "rxjs/operators"; diff --git a/apps/web/src/app/auth/verify-recover-delete.component.ts b/apps/web/src/app/auth/verify-recover-delete.component.ts index 179913d7e32..725f012bf5e 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.ts +++ b/apps/web/src/app/auth/verify-recover-delete.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html index f927a7ca613..2f983944b70 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html @@ -58,7 +58,7 @@

    {{ "paymentType" | i18n }}

    - +